...
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
</manifest>
|
||||
@@ -0,0 +1,2 @@
|
||||
/android_lts_support.o
|
||||
/libandroidltssupport.a
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <errno.h>
|
||||
#include <malloc.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if __ANDROID_API__ < 17
|
||||
|
||||
int posix_memalign(void** memptr, size_t alignment, size_t size) {
|
||||
if ((alignment & (alignment - 1)) != 0 || alignment == 0) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
if (alignment % sizeof(void*) != 0) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
*memptr = memalign(alignment, size);
|
||||
if (*memptr == NULL) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* __ANDROID_API__ < 17 */
|
||||
|
||||
double log2(double x) {
|
||||
return (log(x) / M_LN2);
|
||||
}
|
||||
|
||||
float log2f(float x) {
|
||||
return (float) log2((double) x);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}; /* end of extern "C" */
|
||||
#endif
|
||||
@@ -0,0 +1,931 @@
|
||||
/*
|
||||
* 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
|
||||
double 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 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, double 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, double 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by JNI methods to enable redirection.
|
||||
*/
|
||||
static void enableNativeRedirection() {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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", "(JIFFJDDD)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);
|
||||
|
||||
enableNativeRedirection();
|
||||
|
||||
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) {
|
||||
enableNativeRedirection();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]);
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef FFMPEG_KIT_H
|
||||
#define FFMPEG_KIT_H
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include "libavutil/log.h"
|
||||
#include "libavutil/ffversion.h"
|
||||
|
||||
/** Library version string */
|
||||
#define FFMPEG_KIT_VERSION "6.0"
|
||||
|
||||
/** Defines tag used for Android logging. */
|
||||
#define LIB_NAME "ffmpeg-kit"
|
||||
|
||||
/** 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_ffmpegkit_FFmpegKitConfig
|
||||
* Method: enableNativeRedirection
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_enableNativeRedirection(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
|
||||
* Method: disableNativeRedirection
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_disableNativeRedirection(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
|
||||
* Method: setNativeLogLevel
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_setNativeLogLevel(JNIEnv *, jclass, jint);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
|
||||
* Method: getNativeLogLevel
|
||||
* Signature: ()I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeLogLevel(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
|
||||
* Method: getNativeFFmpegVersion
|
||||
* Signature: ()Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeFFmpegVersion(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
|
||||
* Method: getNativeVersion
|
||||
* Signature: ()Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeVersion(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
|
||||
* Method: nativeFFmpegExecute
|
||||
* Signature: (J[Ljava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpegExecute(JNIEnv *, jclass, jlong, jobjectArray);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
|
||||
* Method: nativeFFmpegCancel
|
||||
* Signature: (J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpegCancel(JNIEnv *, jclass, jlong);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
|
||||
* Method: registerNewNativeFFmpegPipe
|
||||
* Signature: (Ljava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT int JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_registerNewNativeFFmpegPipe(JNIEnv *env, jclass object, jstring ffmpegPipePath);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
|
||||
* Method: getNativeBuildDate
|
||||
* Signature: ()Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeBuildDate(JNIEnv *env, jclass object);
|
||||
|
||||
/**
|
||||
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
|
||||
* Method: setNativeEnvironmentVariable
|
||||
* Signature: (Ljava/lang/String;Ljava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT int JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_setNativeEnvironmentVariable(JNIEnv *env, jclass object, jstring variableName, jstring variableValue);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
|
||||
* Method: ignoreNativeSignal
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_ignoreNativeSignal(JNIEnv *env, jclass object, jint signum);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
|
||||
* Method: messagesInTransmit
|
||||
* Signature: (J)I
|
||||
*/
|
||||
JNIEXPORT int JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_messagesInTransmit(JNIEnv *env, jclass object, jlong id);
|
||||
|
||||
#endif /* FFMPEG_KIT_H */
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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 "cpu-features.h"
|
||||
#include "fftools_ffmpeg.h"
|
||||
#include "ffmpegkit_abidetect.h"
|
||||
|
||||
/** Full name of the Java class that owns native functions in this file. */
|
||||
const char *abiDetectClassName = "com/arthenica/ffmpegkit/AbiDetect";
|
||||
|
||||
/** Prototypes of native functions defined by this file. */
|
||||
JNINativeMethod abiDetectMethods[] = {
|
||||
{"getNativeAbi", "()Ljava/lang/String;", (void*) Java_com_arthenica_ffmpegkit_AbiDetect_getNativeAbi},
|
||||
{"getNativeCpuAbi", "()Ljava/lang/String;", (void*) Java_com_arthenica_ffmpegkit_AbiDetect_getNativeCpuAbi},
|
||||
{"isNativeLTSBuild", "()Z", (void*) Java_com_arthenica_ffmpegkit_AbiDetect_isNativeLTSBuild},
|
||||
{"getNativeBuildConf", "()Ljava/lang/String;", (void*) Java_com_arthenica_ffmpegkit_AbiDetect_getNativeBuildConf}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when 'abidetect' native library is loaded.
|
||||
*
|
||||
* @param vm pointer to the running virtual machine
|
||||
* @param reserved reserved
|
||||
* @return JNI version needed by 'abidetect' 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", abiDetectClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
jclass abiDetectClass = (*env)->FindClass(env, abiDetectClassName);
|
||||
if (abiDetectClass == NULL) {
|
||||
LOGE("OnLoad failed to FindClass %s.\n", abiDetectClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if ((*env)->RegisterNatives(env, abiDetectClass, abiDetectMethods, 4) < 0) {
|
||||
LOGE("OnLoad failed to RegisterNatives for class %s.\n", abiDetectClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns loaded ABI name.
|
||||
*
|
||||
* @param env pointer to native method interface
|
||||
* @param object reference to the class on which this method is invoked
|
||||
* @return loaded ABI name as UTF string
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_AbiDetect_getNativeAbi(JNIEnv *env, jclass object) {
|
||||
|
||||
#ifdef FFMPEG_KIT_ARM_V7A
|
||||
return (*env)->NewStringUTF(env, "arm-v7a");
|
||||
#elif FFMPEG_KIT_ARM64_V8A
|
||||
return (*env)->NewStringUTF(env, "arm64-v8a");
|
||||
#elif FFMPEG_KIT_X86
|
||||
return (*env)->NewStringUTF(env, "x86");
|
||||
#elif FFMPEG_KIT_X86_64
|
||||
return (*env)->NewStringUTF(env, "x86_64");
|
||||
#else
|
||||
return (*env)->NewStringUTF(env, "unknown");
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns ABI name of the running cpu.
|
||||
*
|
||||
* @param env pointer to native method interface
|
||||
* @param object reference to the class on which this method is invoked
|
||||
* @return ABI name of the running cpu as UTF string
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_AbiDetect_getNativeCpuAbi(JNIEnv *env, jclass object) {
|
||||
AndroidCpuFamily family = android_getCpuFamily();
|
||||
|
||||
if (family == ANDROID_CPU_FAMILY_ARM) {
|
||||
uint64_t features = android_getCpuFeatures();
|
||||
|
||||
if (features & ANDROID_CPU_ARM_FEATURE_ARMv7) {
|
||||
if (features & ANDROID_CPU_ARM_FEATURE_NEON) {
|
||||
return (*env)->NewStringUTF(env, ABI_ARMV7A_NEON);
|
||||
} else {
|
||||
return (*env)->NewStringUTF(env, ABI_ARMV7A);
|
||||
}
|
||||
} else {
|
||||
return (*env)->NewStringUTF(env, ABI_ARM);
|
||||
}
|
||||
|
||||
} else if (family == ANDROID_CPU_FAMILY_ARM64) {
|
||||
return (*env)->NewStringUTF(env, ABI_ARM64_V8A);
|
||||
} else if (family == ANDROID_CPU_FAMILY_X86) {
|
||||
return (*env)->NewStringUTF(env, ABI_X86);
|
||||
} else if (family == ANDROID_CPU_FAMILY_X86_64) {
|
||||
return (*env)->NewStringUTF(env, ABI_X86_64);
|
||||
} else {
|
||||
return (*env)->NewStringUTF(env, ABI_UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether FFmpegKit release is a long term release or not.
|
||||
*
|
||||
* @param env pointer to native method interface
|
||||
* @param object reference to the class on which this method is invoked
|
||||
* @return yes or no
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL Java_com_arthenica_ffmpegkit_AbiDetect_isNativeLTSBuild(JNIEnv *env, jclass object) {
|
||||
#if defined(FFMPEG_KIT_LTS)
|
||||
return JNI_TRUE;
|
||||
#else
|
||||
return JNI_FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns build configuration for FFmpeg.
|
||||
*
|
||||
* @param env pointer to native method interface
|
||||
* @param object reference to the class on which this method is invoked
|
||||
* @return build configuration string
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_AbiDetect_getNativeBuildConf(JNIEnv *env, jclass object) {
|
||||
return (*env)->NewStringUTF(env, FFMPEG_CONFIGURATION);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef FFMPEG_KIT_ABIDETECT_H
|
||||
#define FFMPEG_KIT_ABIDETECT_H
|
||||
|
||||
#include <jni.h>
|
||||
#include "ffmpegkit.h"
|
||||
|
||||
/** Represents armeabi-v7a ABI with NEON support. */
|
||||
#define ABI_ARMV7A_NEON "armeabi-v7a-neon"
|
||||
|
||||
/** Represents armeabi-v7a ABI. */
|
||||
#define ABI_ARMV7A "armeabi-v7a"
|
||||
|
||||
/** Represents armeabi ABI. */
|
||||
#define ABI_ARM "armeabi"
|
||||
|
||||
/** Represents x86 ABI. */
|
||||
#define ABI_X86 "x86"
|
||||
|
||||
/** Represents x86_64 ABI. */
|
||||
#define ABI_X86_64 "x86_64"
|
||||
|
||||
/** Represents arm64-v8a ABI. */
|
||||
#define ABI_ARM64_V8A "arm64-v8a"
|
||||
|
||||
/** Represents not supported ABIs. */
|
||||
#define ABI_UNKNOWN "unknown"
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_ffmpegkit_AbiDetect
|
||||
* Method: getNativeAbi
|
||||
* Signature: ()Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_AbiDetect_getNativeAbi(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_ffmpegkit_AbiDetect
|
||||
* Method: getNativeCpuAbi
|
||||
* Signature: ()Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_AbiDetect_getNativeCpuAbi(JNIEnv *, jclass);
|
||||
|
||||
/**
|
||||
* Class: com_arthenica_ffmpegkit_AbiDetect
|
||||
* Method: isNativeLTSBuild
|
||||
* Signature: ()Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL Java_com_arthenica_ffmpegkit_AbiDetect_isNativeLTSBuild(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_ffmpegkit_AbiDetect
|
||||
* Method: getNativeBuildConf
|
||||
* Signature: ()Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_AbiDetect_getNativeBuildConf(JNIEnv *, jclass);
|
||||
|
||||
#endif /* FFMPEG_KIT_ABIDETECT_H */
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 "ffmpegkit_exception.h"
|
||||
|
||||
/** Holds information to implement exception handling. */
|
||||
__thread jmp_buf ex_buf__;
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef FFMPEG_KIT_EXCEPTION_H
|
||||
#define FFMPEG_KIT_EXCEPTION_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
/** Holds information to implement exception handling. */
|
||||
extern __thread jmp_buf ex_buf__;
|
||||
|
||||
#endif // FFMPEG_KIT_EXCEPTION_H
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2021 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "libavcodec/jni.h"
|
||||
#include "libavutil/bprint.h"
|
||||
#include "libavutil/mem.h"
|
||||
#include "ffmpegkit.h"
|
||||
|
||||
/** Forward declaration for function defined in fftools_ffprobe.c */
|
||||
int ffprobe_execute(int argc, char **argv);
|
||||
|
||||
extern int configuredLogLevel;
|
||||
extern __thread long globalSessionId;
|
||||
extern void addSession(long sessionId);
|
||||
extern void removeSession(long sessionId);
|
||||
extern void resetMessagesInTransmit(long sessionId);
|
||||
|
||||
/**
|
||||
* Synchronously executes FFprobe natively with arguments provided.
|
||||
*
|
||||
* @param env pointer to native method interface
|
||||
* @param object reference to the class on which this method is invoked
|
||||
* @param id session id
|
||||
* @param stringArray reference to the object holding FFprobe command arguments
|
||||
* @return zero on successful execution, non-zero on error
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFprobeExecute(JNIEnv *env, jclass object, 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
|
||||
*
|
||||
* ffprobe <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 = ffprobe_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;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2021 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef FFPROBE_KIT_H
|
||||
#define FFPROBE_KIT_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
/*
|
||||
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
|
||||
* Method: nativeFFprobeExecute
|
||||
* Signature: (J[Ljava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFprobeExecute(JNIEnv *, jclass, jlong, jobjectArray);
|
||||
|
||||
#endif /* FFPROBE_KIT_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,516 @@
|
||||
/*
|
||||
* Various utilities for command line tools
|
||||
* copyright (c) 2003 Fabrice Bellard
|
||||
* copyright (c) 2018-2022 Taner Sener
|
||||
* copyright (c) 2023 ARTHENICA LTD
|
||||
*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is the modified version of cmdutils.h file living in ffmpeg source code under the fftools folder. We
|
||||
* manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied
|
||||
* by us to develop mobile-ffmpeg and later ffmpeg-kit libraries.
|
||||
*
|
||||
* ffmpeg-kit changes by ARTHENICA LTD
|
||||
*
|
||||
* 07.2023
|
||||
* --------------------------------------------------------
|
||||
* - FFmpeg 6.0 changes migrated
|
||||
*
|
||||
* mobile-ffmpeg / ffmpeg-kit changes by Taner Sener
|
||||
*
|
||||
* 09.2022
|
||||
* --------------------------------------------------------
|
||||
* - config.h include added back
|
||||
*
|
||||
* 01.2020
|
||||
* --------------------------------------------------------
|
||||
* - ffprobe support added (variables used by ffprobe marked with "__thread" specifier)
|
||||
* - AV_LOG_STDERR log level added
|
||||
*
|
||||
* 12.2019
|
||||
* --------------------------------------------------------
|
||||
* - concurrent execution support ("__thread" specifier added to variables used by multiple threads)
|
||||
*
|
||||
* 03.2019
|
||||
* --------------------------------------------------------
|
||||
* - config.h include removed
|
||||
*
|
||||
* 08.2018
|
||||
* --------------------------------------------------------
|
||||
* - fftools_ prefix added to file name and include guards
|
||||
*
|
||||
* 07.2018
|
||||
* --------------------------------------------------------
|
||||
* - include guards renamed
|
||||
* - unused headers removed
|
||||
*/
|
||||
|
||||
#ifndef FFTOOLS_CMDUTILS_H
|
||||
#define FFTOOLS_CMDUTILS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavfilter/avfilter.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libswscale/swscale.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#undef main /* We don't want SDL to override our main() */
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Defines logs printed to stderr by ffmpeg. They are not filtered and always redirected.
|
||||
*/
|
||||
#define AV_LOG_STDERR -16
|
||||
|
||||
/**
|
||||
* program name, defined by the program for show_version().
|
||||
*/
|
||||
extern __thread char *program_name;
|
||||
|
||||
/**
|
||||
* program birth year, defined by the program for show_banner()
|
||||
*/
|
||||
extern __thread int program_birth_year;
|
||||
|
||||
extern __thread AVDictionary *sws_dict;
|
||||
extern __thread AVDictionary *swr_opts;
|
||||
extern __thread AVDictionary *format_opts, *codec_opts;
|
||||
extern __thread int hide_banner;
|
||||
extern __thread int find_stream_info;
|
||||
|
||||
/**
|
||||
* Register a program-specific cleanup routine.
|
||||
*/
|
||||
void register_exit(void (*cb)(int ret));
|
||||
|
||||
/**
|
||||
* Reports an error corresponding to the provided
|
||||
* AVERROR code and calls exit_program() with the
|
||||
* corresponding POSIX error code.
|
||||
* @note ret must be an AVERROR-value of a POSIX error code
|
||||
* (i.e. AVERROR(EFOO) and not AVERROR_FOO).
|
||||
* library functions can return both, so call this only
|
||||
* with AVERROR(EFOO) of your own.
|
||||
*/
|
||||
void report_and_exit(int ret) av_noreturn;
|
||||
|
||||
/**
|
||||
* Wraps exit with a program-specific cleanup routine.
|
||||
*/
|
||||
void exit_program(int ret) av_noreturn;
|
||||
|
||||
/**
|
||||
* Initialize dynamic library loading
|
||||
*/
|
||||
void init_dynload(void);
|
||||
|
||||
/**
|
||||
* Uninitialize the cmdutils option system, in particular
|
||||
* free the *_opts contexts and their contents.
|
||||
*/
|
||||
void uninit_opts(void);
|
||||
|
||||
/**
|
||||
* Trivial log callback.
|
||||
* Only suitable for opt_help and similar since it lacks prefix handling.
|
||||
*/
|
||||
void log_callback_help(void* ptr, int level, const char* fmt, va_list vl);
|
||||
|
||||
/**
|
||||
* Fallback for options that are not explicitly handled, these will be
|
||||
* parsed through AVOptions.
|
||||
*/
|
||||
int opt_default(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Limit the execution time.
|
||||
*/
|
||||
int opt_timelimit(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Parse a string and return its corresponding value as a double.
|
||||
* Exit from the application if the string cannot be correctly
|
||||
* parsed or the corresponding value is invalid.
|
||||
*
|
||||
* @param context the context of the value to be set (e.g. the
|
||||
* corresponding command line option name)
|
||||
* @param numstr the string to be parsed
|
||||
* @param type the type (OPT_INT64 or OPT_FLOAT) as which the
|
||||
* string should be parsed
|
||||
* @param min the minimum valid accepted value
|
||||
* @param max the maximum valid accepted value
|
||||
*/
|
||||
double parse_number_or_die(const char *context, const char *numstr, int type,
|
||||
double min, double max);
|
||||
|
||||
/**
|
||||
* Parse a string specifying a time and return its corresponding
|
||||
* value as a number of microseconds. Exit from the application if
|
||||
* the string cannot be correctly parsed.
|
||||
*
|
||||
* @param context the context of the value to be set (e.g. the
|
||||
* corresponding command line option name)
|
||||
* @param timestr the string to be parsed
|
||||
* @param is_duration a flag which tells how to interpret timestr, if
|
||||
* not zero timestr is interpreted as a duration, otherwise as a
|
||||
* date
|
||||
*
|
||||
* @see av_parse_time()
|
||||
*/
|
||||
int64_t parse_time_or_die(const char *context, const char *timestr,
|
||||
int is_duration);
|
||||
|
||||
typedef struct SpecifierOpt {
|
||||
char *specifier; /**< stream/chapter/program/... specifier */
|
||||
union {
|
||||
uint8_t *str;
|
||||
int i;
|
||||
int64_t i64;
|
||||
uint64_t ui64;
|
||||
float f;
|
||||
double dbl;
|
||||
} u;
|
||||
} SpecifierOpt;
|
||||
|
||||
typedef struct OptionDef {
|
||||
const char *name;
|
||||
int flags;
|
||||
#define HAS_ARG 0x0001
|
||||
#define OPT_BOOL 0x0002
|
||||
#define OPT_EXPERT 0x0004
|
||||
#define OPT_STRING 0x0008
|
||||
#define OPT_VIDEO 0x0010
|
||||
#define OPT_AUDIO 0x0020
|
||||
#define OPT_INT 0x0080
|
||||
#define OPT_FLOAT 0x0100
|
||||
#define OPT_SUBTITLE 0x0200
|
||||
#define OPT_INT64 0x0400
|
||||
#define OPT_EXIT 0x0800
|
||||
#define OPT_DATA 0x1000
|
||||
#define OPT_PERFILE 0x2000 /* the option is per-file (currently ffmpeg-only).
|
||||
implied by OPT_OFFSET or OPT_SPEC */
|
||||
#define OPT_OFFSET 0x4000 /* option is specified as an offset in a passed optctx */
|
||||
#define OPT_SPEC 0x8000 /* option is to be stored in an array of SpecifierOpt.
|
||||
Implies OPT_OFFSET. Next element after the offset is
|
||||
an int containing element count in the array. */
|
||||
#define OPT_TIME 0x10000
|
||||
#define OPT_DOUBLE 0x20000
|
||||
#define OPT_INPUT 0x40000
|
||||
#define OPT_OUTPUT 0x80000
|
||||
union {
|
||||
void *dst_ptr;
|
||||
int (*func_arg)(void *, const char *, const char *);
|
||||
size_t off;
|
||||
} u;
|
||||
const char *help;
|
||||
const char *argname;
|
||||
} OptionDef;
|
||||
|
||||
/**
|
||||
* Print help for all options matching specified flags.
|
||||
*
|
||||
* @param options a list of options
|
||||
* @param msg title of this group. Only printed if at least one option matches.
|
||||
* @param req_flags print only options which have all those flags set.
|
||||
* @param rej_flags don't print options which have any of those flags set.
|
||||
* @param alt_flags print only options that have at least one of those flags set
|
||||
*/
|
||||
void show_help_options(const OptionDef *options, const char *msg, int req_flags,
|
||||
int rej_flags, int alt_flags);
|
||||
|
||||
/**
|
||||
* Show help for all options with given flags in class and all its
|
||||
* children.
|
||||
*/
|
||||
void show_help_children(const AVClass *clazz, int flags);
|
||||
|
||||
/**
|
||||
* Per-fftool specific help handler. Implemented in each
|
||||
* fftool, called by show_help().
|
||||
*/
|
||||
void show_help_default_ffmpeg(const char *opt, const char *arg);
|
||||
void show_help_default_ffprobe(const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Parse the command line arguments.
|
||||
*
|
||||
* @param optctx an opaque options context
|
||||
* @param argc number of command line arguments
|
||||
* @param argv values of command line arguments
|
||||
* @param options Array with the definitions required to interpret every
|
||||
* option of the form: -option_name [argument]
|
||||
* @param parse_arg_function Name of the function called to process every
|
||||
* argument without a leading option name flag. NULL if such arguments do
|
||||
* not have to be processed.
|
||||
*/
|
||||
void parse_options(void *optctx, int argc, char **argv, const OptionDef *options,
|
||||
void (* parse_arg_function)(void *optctx, const char*));
|
||||
|
||||
/**
|
||||
* Parse one given option.
|
||||
*
|
||||
* @return on success 1 if arg was consumed, 0 otherwise; negative number on error
|
||||
*/
|
||||
int parse_option(void *optctx, const char *opt, const char *arg,
|
||||
const OptionDef *options);
|
||||
|
||||
/**
|
||||
* An option extracted from the commandline.
|
||||
* Cannot use AVDictionary because of options like -map which can be
|
||||
* used multiple times.
|
||||
*/
|
||||
typedef struct Option {
|
||||
const OptionDef *opt;
|
||||
const char *key;
|
||||
const char *val;
|
||||
} Option;
|
||||
|
||||
typedef struct OptionGroupDef {
|
||||
/**< group name */
|
||||
const char *name;
|
||||
/**
|
||||
* Option to be used as group separator. Can be NULL for groups which
|
||||
* are terminated by a non-option argument (e.g. ffmpeg output files)
|
||||
*/
|
||||
const char *sep;
|
||||
/**
|
||||
* Option flags that must be set on each option that is
|
||||
* applied to this group
|
||||
*/
|
||||
int flags;
|
||||
} OptionGroupDef;
|
||||
|
||||
typedef struct OptionGroup {
|
||||
const OptionGroupDef *group_def;
|
||||
const char *arg;
|
||||
|
||||
Option *opts;
|
||||
int nb_opts;
|
||||
|
||||
AVDictionary *codec_opts;
|
||||
AVDictionary *format_opts;
|
||||
AVDictionary *sws_dict;
|
||||
AVDictionary *swr_opts;
|
||||
} OptionGroup;
|
||||
|
||||
/**
|
||||
* A list of option groups that all have the same group type
|
||||
* (e.g. input files or output files)
|
||||
*/
|
||||
typedef struct OptionGroupList {
|
||||
const OptionGroupDef *group_def;
|
||||
|
||||
OptionGroup *groups;
|
||||
int nb_groups;
|
||||
} OptionGroupList;
|
||||
|
||||
typedef struct OptionParseContext {
|
||||
OptionGroup global_opts;
|
||||
|
||||
OptionGroupList *groups;
|
||||
int nb_groups;
|
||||
|
||||
/* parsing state */
|
||||
OptionGroup cur_group;
|
||||
} OptionParseContext;
|
||||
|
||||
/**
|
||||
* Parse an options group and write results into optctx.
|
||||
*
|
||||
* @param optctx an app-specific options context. NULL for global options group
|
||||
* @param g option group
|
||||
*/
|
||||
int parse_optgroup(void *optctx, OptionGroup *g);
|
||||
|
||||
/**
|
||||
* Split the commandline into an intermediate form convenient for further
|
||||
* processing.
|
||||
*
|
||||
* The commandline is assumed to be composed of options which either belong to a
|
||||
* group (those with OPT_SPEC, OPT_OFFSET or OPT_PERFILE) or are global
|
||||
* (everything else).
|
||||
*
|
||||
* A group (defined by an OptionGroupDef struct) is a sequence of options
|
||||
* terminated by either a group separator option (e.g. -i) or a parameter that
|
||||
* is not an option (doesn't start with -). A group without a separator option
|
||||
* must always be first in the supplied groups list.
|
||||
*
|
||||
* All options within the same group are stored in one OptionGroup struct in an
|
||||
* OptionGroupList, all groups with the same group definition are stored in one
|
||||
* OptionGroupList in OptionParseContext.groups. The order of group lists is the
|
||||
* same as the order of group definitions.
|
||||
*/
|
||||
int split_commandline(OptionParseContext *octx, int argc, char *argv[],
|
||||
const OptionDef *options,
|
||||
const OptionGroupDef *groups, int nb_groups);
|
||||
|
||||
/**
|
||||
* Free all allocated memory in an OptionParseContext.
|
||||
*/
|
||||
void uninit_parse_context(OptionParseContext *octx);
|
||||
|
||||
/**
|
||||
* Find the '-loglevel' option in the command line args and apply it.
|
||||
*/
|
||||
void parse_loglevel(int argc, char **argv, const OptionDef *options);
|
||||
|
||||
/**
|
||||
* Return index of option opt in argv or 0 if not found.
|
||||
*/
|
||||
int locate_option(int argc, char **argv, const OptionDef *options,
|
||||
const char *optname);
|
||||
|
||||
/**
|
||||
* Check if the given stream matches a stream specifier.
|
||||
*
|
||||
* @param s Corresponding format context.
|
||||
* @param st Stream from s to be checked.
|
||||
* @param spec A stream specifier of the [v|a|s|d]:[\<stream index\>] form.
|
||||
*
|
||||
* @return 1 if the stream matches, 0 if it doesn't, <0 on error
|
||||
*/
|
||||
int check_stream_specifier(AVFormatContext *s, AVStream *st, const char *spec);
|
||||
|
||||
/**
|
||||
* Filter out options for given codec.
|
||||
*
|
||||
* Create a new options dictionary containing only the options from
|
||||
* opts which apply to the codec with ID codec_id.
|
||||
*
|
||||
* @param opts dictionary to place options in
|
||||
* @param codec_id ID of the codec that should be filtered for
|
||||
* @param s Corresponding format context.
|
||||
* @param st A stream from s for which the options should be filtered.
|
||||
* @param codec The particular codec for which the options should be filtered.
|
||||
* If null, the default one is looked up according to the codec id.
|
||||
* @return a pointer to the created dictionary
|
||||
*/
|
||||
AVDictionary *filter_codec_opts(AVDictionary *opts, enum AVCodecID codec_id,
|
||||
AVFormatContext *s, AVStream *st, const AVCodec *codec);
|
||||
|
||||
/**
|
||||
* Setup AVCodecContext options for avformat_find_stream_info().
|
||||
*
|
||||
* Create an array of dictionaries, one dictionary for each stream
|
||||
* contained in s.
|
||||
* Each dictionary will contain the options from codec_opts which can
|
||||
* be applied to the corresponding stream codec context.
|
||||
*
|
||||
* @return pointer to the created array of dictionaries.
|
||||
* Calls exit() on failure.
|
||||
*/
|
||||
AVDictionary **setup_find_stream_info_opts(AVFormatContext *s,
|
||||
AVDictionary *codec_opts);
|
||||
|
||||
/**
|
||||
* Print an error message to stderr, indicating filename and a human
|
||||
* readable description of the error code err.
|
||||
*
|
||||
* If strerror_r() is not available the use of this function in a
|
||||
* multithreaded application may be unsafe.
|
||||
*
|
||||
* @see av_strerror()
|
||||
*/
|
||||
void print_error(const char *filename, int err);
|
||||
|
||||
/**
|
||||
* Print the program banner to stderr. The banner contents depend on the
|
||||
* current version of the repository and of the libav* libraries used by
|
||||
* the program.
|
||||
*/
|
||||
void show_banner(int argc, char **argv, const OptionDef *options);
|
||||
|
||||
/**
|
||||
* Return a positive value if a line read from standard input
|
||||
* starts with [yY], otherwise return 0.
|
||||
*/
|
||||
int read_yesno(void);
|
||||
|
||||
/**
|
||||
* Get a file corresponding to a preset file.
|
||||
*
|
||||
* If is_path is non-zero, look for the file in the path preset_name.
|
||||
* Otherwise search for a file named arg.ffpreset in the directories
|
||||
* $FFMPEG_DATADIR (if set), $HOME/.ffmpeg, and in the datadir defined
|
||||
* at configuration time or in a "ffpresets" folder along the executable
|
||||
* on win32, in that order. If no such file is found and
|
||||
* codec_name is defined, then search for a file named
|
||||
* codec_name-preset_name.avpreset in the above-mentioned directories.
|
||||
*
|
||||
* @param filename buffer where the name of the found filename is written
|
||||
* @param filename_size size in bytes of the filename buffer
|
||||
* @param preset_name name of the preset to search
|
||||
* @param is_path tell if preset_name is a filename path
|
||||
* @param codec_name name of the codec for which to look for the
|
||||
* preset, may be NULL
|
||||
*/
|
||||
FILE *get_preset_file(char *filename, size_t filename_size,
|
||||
const char *preset_name, int is_path, const char *codec_name);
|
||||
|
||||
/**
|
||||
* Realloc array to hold new_size elements of elem_size.
|
||||
* Calls exit() on failure.
|
||||
*
|
||||
* @param array array to reallocate
|
||||
* @param elem_size size in bytes of each element
|
||||
* @param size new element count will be written here
|
||||
* @param new_size number of elements to place in reallocated array
|
||||
* @return reallocated array
|
||||
*/
|
||||
void *grow_array(void *array, int elem_size, int *size, int new_size);
|
||||
|
||||
/**
|
||||
* Atomically add a new element to an array of pointers, i.e. allocate
|
||||
* a new entry, reallocate the array of pointers and make the new last
|
||||
* member of this array point to the newly allocated buffer.
|
||||
* Calls exit() on failure.
|
||||
*
|
||||
* @param array array of pointers to reallocate
|
||||
* @param elem_size size of the new element to allocate
|
||||
* @param nb_elems pointer to the number of elements of the array array;
|
||||
* *nb_elems will be incremented by one by this function.
|
||||
* @return pointer to the newly allocated entry
|
||||
*/
|
||||
void *allocate_array_elem(void *array, size_t elem_size, int *nb_elems);
|
||||
|
||||
#define GROW_ARRAY(array, nb_elems)\
|
||||
array = grow_array(array, sizeof(*array), &nb_elems, nb_elems + 1)
|
||||
|
||||
#define ALLOC_ARRAY_ELEM(array, nb_elems)\
|
||||
allocate_array_elem(&array, sizeof(*array[0]), &nb_elems)
|
||||
|
||||
#define GET_PIX_FMT_NAME(pix_fmt)\
|
||||
const char *name = av_get_pix_fmt_name(pix_fmt);
|
||||
|
||||
#define GET_CODEC_NAME(id)\
|
||||
const char *name = avcodec_descriptor_get(id)->name;
|
||||
|
||||
#define GET_SAMPLE_FMT_NAME(sample_fmt)\
|
||||
const char *name = av_get_sample_fmt_name(sample_fmt)
|
||||
|
||||
#define GET_SAMPLE_RATE_NAME(rate)\
|
||||
char name[16];\
|
||||
snprintf(name, sizeof(name), "%d", rate);
|
||||
|
||||
double get_rotation(int32_t *displaymatrix);
|
||||
|
||||
#endif /* FFTOOLS_CMDUTILS_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,912 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
* Copyright (c) 2018-2022 Taner Sener
|
||||
* Copyright (c) 2023 ARTHENICA LTD
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is the modified version of ffmpeg.h file living in ffmpeg source code under the fftools folder. We
|
||||
* manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied
|
||||
* by us to develop mobile-ffmpeg and later ffmpeg-kit libraries.
|
||||
*
|
||||
* ffmpeg-kit changes by ARTHENICA LTD
|
||||
*
|
||||
* 07.2023
|
||||
* --------------------------------------------------------
|
||||
* - FFmpeg 6.0 changes migrated
|
||||
* - WARN_MULTIPLE_OPT_USAGE, MATCH_PER_STREAM_OPT, MATCH_PER_TYPE_OPT, SPECIFIER_OPT_FMT declarations migrated to
|
||||
* ffmpeg_mux.h
|
||||
* - "class" member field renamed as clazz
|
||||
* - time field in set_report_callback updated as double
|
||||
*
|
||||
* mobile-ffmpeg / ffmpeg-kit changes by Taner Sener
|
||||
*
|
||||
* 09.2022
|
||||
* --------------------------------------------------------
|
||||
* - config.h include added back
|
||||
* - volatile dropped from thread local variables
|
||||
* - dropped signatures of ffmpeg_opt.c methods called by both ffmpeg and ffprobe
|
||||
*
|
||||
* 06.2020
|
||||
* --------------------------------------------------------
|
||||
* - cancel_operation() method signature updated with id
|
||||
*
|
||||
* 12.2019
|
||||
* --------------------------------------------------------
|
||||
* - concurrent execution support ("__thread" specifier added to variables used by multiple threads,
|
||||
* signatures of ffmpeg_opt.c methods called by both ffmpeg and ffprobe added)
|
||||
*
|
||||
* 03.2019
|
||||
* --------------------------------------------------------
|
||||
* - config.h include removed
|
||||
*
|
||||
* 08.2018
|
||||
* --------------------------------------------------------
|
||||
* - fftools_ prefix added to file name and include guards
|
||||
* - set_report_callback() method declared
|
||||
* - cancel_operation() method declared
|
||||
*
|
||||
* 07.2018
|
||||
* --------------------------------------------------------
|
||||
* - include guards renamed
|
||||
*/
|
||||
|
||||
#ifndef FFTOOLS_FFMPEG_H
|
||||
#define FFTOOLS_FFMPEG_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "fftools_cmdutils.h"
|
||||
#include "fftools_sync_queue.h"
|
||||
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavformat/avio.h"
|
||||
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavcodec/bsf.h"
|
||||
|
||||
#include "libavfilter/avfilter.h"
|
||||
|
||||
#include "libavutil/avutil.h"
|
||||
#include "libavutil/dict.h"
|
||||
#include "libavutil/eval.h"
|
||||
#include "libavutil/fifo.h"
|
||||
#include "libavutil/hwcontext.h"
|
||||
#include "libavutil/pixfmt.h"
|
||||
#include "libavutil/rational.h"
|
||||
#include "libavutil/thread.h"
|
||||
#include "libavutil/threadmessage.h"
|
||||
|
||||
#include "libswresample/swresample.h"
|
||||
|
||||
// deprecated features
|
||||
#define FFMPEG_OPT_PSNR 1
|
||||
#define FFMPEG_OPT_MAP_CHANNEL 1
|
||||
#define FFMPEG_OPT_MAP_SYNC 1
|
||||
#define FFMPEG_ROTATION_METADATA 1
|
||||
|
||||
enum VideoSyncMethod {
|
||||
VSYNC_AUTO = -1,
|
||||
VSYNC_PASSTHROUGH,
|
||||
VSYNC_CFR,
|
||||
VSYNC_VFR,
|
||||
VSYNC_VSCFR,
|
||||
VSYNC_DROP,
|
||||
};
|
||||
|
||||
#define MAX_STREAMS 1024 /* arbitrary sanity check value */
|
||||
|
||||
enum HWAccelID {
|
||||
HWACCEL_NONE = 0,
|
||||
HWACCEL_AUTO,
|
||||
HWACCEL_GENERIC,
|
||||
};
|
||||
|
||||
typedef struct HWDevice {
|
||||
const char *name;
|
||||
enum AVHWDeviceType type;
|
||||
AVBufferRef *device_ref;
|
||||
} HWDevice;
|
||||
|
||||
/* select an input stream for an output stream */
|
||||
typedef struct StreamMap {
|
||||
int disabled; /* 1 is this mapping is disabled by a negative map */
|
||||
int file_index;
|
||||
int stream_index;
|
||||
char *linklabel; /* name of an output link, for mapping lavfi outputs */
|
||||
} StreamMap;
|
||||
|
||||
#if FFMPEG_OPT_MAP_CHANNEL
|
||||
typedef struct {
|
||||
int file_idx, stream_idx, channel_idx; // input
|
||||
int ofile_idx, ostream_idx; // output
|
||||
} AudioChannelMap;
|
||||
#endif
|
||||
|
||||
typedef struct OptionsContext {
|
||||
OptionGroup *g;
|
||||
|
||||
/* input/output options */
|
||||
int64_t start_time;
|
||||
int64_t start_time_eof;
|
||||
int seek_timestamp;
|
||||
const char *format;
|
||||
|
||||
SpecifierOpt *codec_names;
|
||||
int nb_codec_names;
|
||||
SpecifierOpt *audio_ch_layouts;
|
||||
int nb_audio_ch_layouts;
|
||||
SpecifierOpt *audio_channels;
|
||||
int nb_audio_channels;
|
||||
SpecifierOpt *audio_sample_rate;
|
||||
int nb_audio_sample_rate;
|
||||
SpecifierOpt *frame_rates;
|
||||
int nb_frame_rates;
|
||||
SpecifierOpt *max_frame_rates;
|
||||
int nb_max_frame_rates;
|
||||
SpecifierOpt *frame_sizes;
|
||||
int nb_frame_sizes;
|
||||
SpecifierOpt *frame_pix_fmts;
|
||||
int nb_frame_pix_fmts;
|
||||
|
||||
/* input options */
|
||||
int64_t input_ts_offset;
|
||||
int loop;
|
||||
int rate_emu;
|
||||
float readrate;
|
||||
int accurate_seek;
|
||||
int thread_queue_size;
|
||||
int input_sync_ref;
|
||||
int find_stream_info;
|
||||
|
||||
SpecifierOpt *ts_scale;
|
||||
int nb_ts_scale;
|
||||
SpecifierOpt *dump_attachment;
|
||||
int nb_dump_attachment;
|
||||
SpecifierOpt *hwaccels;
|
||||
int nb_hwaccels;
|
||||
SpecifierOpt *hwaccel_devices;
|
||||
int nb_hwaccel_devices;
|
||||
SpecifierOpt *hwaccel_output_formats;
|
||||
int nb_hwaccel_output_formats;
|
||||
SpecifierOpt *autorotate;
|
||||
int nb_autorotate;
|
||||
|
||||
/* output options */
|
||||
StreamMap *stream_maps;
|
||||
int nb_stream_maps;
|
||||
#if FFMPEG_OPT_MAP_CHANNEL
|
||||
AudioChannelMap *audio_channel_maps; /* one info entry per -map_channel */
|
||||
int nb_audio_channel_maps; /* number of (valid) -map_channel settings */
|
||||
#endif
|
||||
const char **attachments;
|
||||
int nb_attachments;
|
||||
|
||||
int chapters_input_file;
|
||||
|
||||
int64_t recording_time;
|
||||
int64_t stop_time;
|
||||
int64_t limit_filesize;
|
||||
float mux_preload;
|
||||
float mux_max_delay;
|
||||
float shortest_buf_duration;
|
||||
int shortest;
|
||||
int bitexact;
|
||||
|
||||
int video_disable;
|
||||
int audio_disable;
|
||||
int subtitle_disable;
|
||||
int data_disable;
|
||||
|
||||
/* indexed by output file stream index */
|
||||
int *streamid_map;
|
||||
int nb_streamid_map;
|
||||
|
||||
SpecifierOpt *metadata;
|
||||
int nb_metadata;
|
||||
SpecifierOpt *max_frames;
|
||||
int nb_max_frames;
|
||||
SpecifierOpt *bitstream_filters;
|
||||
int nb_bitstream_filters;
|
||||
SpecifierOpt *codec_tags;
|
||||
int nb_codec_tags;
|
||||
SpecifierOpt *sample_fmts;
|
||||
int nb_sample_fmts;
|
||||
SpecifierOpt *qscale;
|
||||
int nb_qscale;
|
||||
SpecifierOpt *forced_key_frames;
|
||||
int nb_forced_key_frames;
|
||||
SpecifierOpt *fps_mode;
|
||||
int nb_fps_mode;
|
||||
SpecifierOpt *force_fps;
|
||||
int nb_force_fps;
|
||||
SpecifierOpt *frame_aspect_ratios;
|
||||
int nb_frame_aspect_ratios;
|
||||
SpecifierOpt *display_rotations;
|
||||
int nb_display_rotations;
|
||||
SpecifierOpt *display_hflips;
|
||||
int nb_display_hflips;
|
||||
SpecifierOpt *display_vflips;
|
||||
int nb_display_vflips;
|
||||
SpecifierOpt *rc_overrides;
|
||||
int nb_rc_overrides;
|
||||
SpecifierOpt *intra_matrices;
|
||||
int nb_intra_matrices;
|
||||
SpecifierOpt *inter_matrices;
|
||||
int nb_inter_matrices;
|
||||
SpecifierOpt *chroma_intra_matrices;
|
||||
int nb_chroma_intra_matrices;
|
||||
SpecifierOpt *top_field_first;
|
||||
int nb_top_field_first;
|
||||
SpecifierOpt *metadata_map;
|
||||
int nb_metadata_map;
|
||||
SpecifierOpt *presets;
|
||||
int nb_presets;
|
||||
SpecifierOpt *copy_initial_nonkeyframes;
|
||||
int nb_copy_initial_nonkeyframes;
|
||||
SpecifierOpt *copy_prior_start;
|
||||
int nb_copy_prior_start;
|
||||
SpecifierOpt *filters;
|
||||
int nb_filters;
|
||||
SpecifierOpt *filter_scripts;
|
||||
int nb_filter_scripts;
|
||||
SpecifierOpt *reinit_filters;
|
||||
int nb_reinit_filters;
|
||||
SpecifierOpt *fix_sub_duration;
|
||||
int nb_fix_sub_duration;
|
||||
SpecifierOpt *fix_sub_duration_heartbeat;
|
||||
int nb_fix_sub_duration_heartbeat;
|
||||
SpecifierOpt *canvas_sizes;
|
||||
int nb_canvas_sizes;
|
||||
SpecifierOpt *pass;
|
||||
int nb_pass;
|
||||
SpecifierOpt *passlogfiles;
|
||||
int nb_passlogfiles;
|
||||
SpecifierOpt *max_muxing_queue_size;
|
||||
int nb_max_muxing_queue_size;
|
||||
SpecifierOpt *muxing_queue_data_threshold;
|
||||
int nb_muxing_queue_data_threshold;
|
||||
SpecifierOpt *guess_layout_max;
|
||||
int nb_guess_layout_max;
|
||||
SpecifierOpt *apad;
|
||||
int nb_apad;
|
||||
SpecifierOpt *discard;
|
||||
int nb_discard;
|
||||
SpecifierOpt *disposition;
|
||||
int nb_disposition;
|
||||
SpecifierOpt *program;
|
||||
int nb_program;
|
||||
SpecifierOpt *time_bases;
|
||||
int nb_time_bases;
|
||||
SpecifierOpt *enc_time_bases;
|
||||
int nb_enc_time_bases;
|
||||
SpecifierOpt *autoscale;
|
||||
int nb_autoscale;
|
||||
SpecifierOpt *bits_per_raw_sample;
|
||||
int nb_bits_per_raw_sample;
|
||||
SpecifierOpt *enc_stats_pre;
|
||||
int nb_enc_stats_pre;
|
||||
SpecifierOpt *enc_stats_post;
|
||||
int nb_enc_stats_post;
|
||||
SpecifierOpt *mux_stats;
|
||||
int nb_mux_stats;
|
||||
SpecifierOpt *enc_stats_pre_fmt;
|
||||
int nb_enc_stats_pre_fmt;
|
||||
SpecifierOpt *enc_stats_post_fmt;
|
||||
int nb_enc_stats_post_fmt;
|
||||
SpecifierOpt *mux_stats_fmt;
|
||||
int nb_mux_stats_fmt;
|
||||
} OptionsContext;
|
||||
|
||||
typedef struct InputFilter {
|
||||
AVFilterContext *filter;
|
||||
struct InputStream *ist;
|
||||
struct FilterGraph *graph;
|
||||
uint8_t *name;
|
||||
enum AVMediaType type; // AVMEDIA_TYPE_SUBTITLE for sub2video
|
||||
|
||||
AVFifo *frame_queue;
|
||||
|
||||
// parameters configured for this input
|
||||
int format;
|
||||
|
||||
int width, height;
|
||||
AVRational sample_aspect_ratio;
|
||||
|
||||
int sample_rate;
|
||||
AVChannelLayout ch_layout;
|
||||
|
||||
AVBufferRef *hw_frames_ctx;
|
||||
int32_t *displaymatrix;
|
||||
|
||||
int eof;
|
||||
} InputFilter;
|
||||
|
||||
typedef struct OutputFilter {
|
||||
AVFilterContext *filter;
|
||||
struct OutputStream *ost;
|
||||
struct FilterGraph *graph;
|
||||
uint8_t *name;
|
||||
|
||||
/* temporary storage until stream maps are processed */
|
||||
AVFilterInOut *out_tmp;
|
||||
enum AVMediaType type;
|
||||
|
||||
/* desired output stream properties */
|
||||
int width, height;
|
||||
AVRational frame_rate;
|
||||
int format;
|
||||
int sample_rate;
|
||||
AVChannelLayout ch_layout;
|
||||
|
||||
// those are only set if no format is specified and the encoder gives us multiple options
|
||||
// They point directly to the relevant lists of the encoder.
|
||||
const int *formats;
|
||||
const AVChannelLayout *ch_layouts;
|
||||
const int *sample_rates;
|
||||
} OutputFilter;
|
||||
|
||||
typedef struct FilterGraph {
|
||||
int index;
|
||||
const char *graph_desc;
|
||||
|
||||
AVFilterGraph *graph;
|
||||
int reconfiguration;
|
||||
// true when the filtergraph contains only meta filters
|
||||
// that do not modify the frame data
|
||||
int is_meta;
|
||||
|
||||
InputFilter **inputs;
|
||||
int nb_inputs;
|
||||
OutputFilter **outputs;
|
||||
int nb_outputs;
|
||||
} FilterGraph;
|
||||
|
||||
typedef struct InputStream {
|
||||
int file_index;
|
||||
AVStream *st;
|
||||
int discard; /* true if stream data should be discarded */
|
||||
int user_set_discard;
|
||||
int decoding_needed; /* non zero if the packets must be decoded in 'raw_fifo', see DECODING_FOR_* */
|
||||
#define DECODING_FOR_OST 1
|
||||
#define DECODING_FOR_FILTER 2
|
||||
int processing_needed; /* non zero if the packets must be processed */
|
||||
// should attach FrameData as opaque_ref after decoding
|
||||
int want_frame_data;
|
||||
|
||||
/**
|
||||
* Codec parameters - to be used by the decoding/streamcopy code.
|
||||
* st->codecpar should not be accessed, because it may be modified
|
||||
* concurrently by the demuxing thread.
|
||||
*/
|
||||
AVCodecParameters *par;
|
||||
AVCodecContext *dec_ctx;
|
||||
const AVCodec *dec;
|
||||
AVFrame *decoded_frame;
|
||||
AVPacket *pkt;
|
||||
|
||||
AVRational framerate_guessed;
|
||||
|
||||
int64_t prev_pkt_pts;
|
||||
int64_t start; /* time when read started */
|
||||
/* predicted dts of the next packet read for this stream or (when there are
|
||||
* several frames in a packet) of the next frame in current packet (in AV_TIME_BASE units) */
|
||||
int64_t next_dts;
|
||||
int64_t first_dts; ///< dts of the first packet read for this stream (in AV_TIME_BASE units)
|
||||
int64_t dts; ///< dts of the last packet read for this stream (in AV_TIME_BASE units)
|
||||
|
||||
int64_t next_pts; ///< synthetic pts for the next decode frame (in AV_TIME_BASE units)
|
||||
int64_t pts; ///< current pts of the decoded frame (in AV_TIME_BASE units)
|
||||
int wrap_correction_done;
|
||||
|
||||
// the value of AVCodecParserContext.repeat_pict from the AVStream parser
|
||||
// for the last packet returned from ifile_get_packet()
|
||||
// -1 if unknown
|
||||
// FIXME: this is a hack, the avstream parser should not be used
|
||||
int last_pkt_repeat_pict;
|
||||
|
||||
int64_t filter_in_rescale_delta_last;
|
||||
|
||||
int64_t min_pts; /* pts with the smallest value in a current stream */
|
||||
int64_t max_pts; /* pts with the higher value in a current stream */
|
||||
|
||||
// when forcing constant input framerate through -r,
|
||||
// this contains the pts that will be given to the next decoded frame
|
||||
int64_t cfr_next_pts;
|
||||
|
||||
int64_t nb_samples; /* number of samples in the last decoded audio frame before looping */
|
||||
|
||||
double ts_scale;
|
||||
int saw_first_ts;
|
||||
AVDictionary *decoder_opts;
|
||||
AVRational framerate; /* framerate forced with -r */
|
||||
int top_field_first;
|
||||
int guess_layout_max;
|
||||
|
||||
int autorotate;
|
||||
|
||||
int fix_sub_duration;
|
||||
struct { /* previous decoded subtitle and related variables */
|
||||
int got_output;
|
||||
int ret;
|
||||
AVSubtitle subtitle;
|
||||
} prev_sub;
|
||||
|
||||
struct sub2video {
|
||||
int64_t last_pts;
|
||||
int64_t end_pts;
|
||||
AVFifo *sub_queue; ///< queue of AVSubtitle* before filter init
|
||||
AVFrame *frame;
|
||||
int w, h;
|
||||
unsigned int initialize; ///< marks if sub2video_update should force an initialization
|
||||
} sub2video;
|
||||
|
||||
/* decoded data from this stream goes into all those filters
|
||||
* currently video and audio only */
|
||||
InputFilter **filters;
|
||||
int nb_filters;
|
||||
|
||||
int reinit_filters;
|
||||
|
||||
/* hwaccel options */
|
||||
enum HWAccelID hwaccel_id;
|
||||
enum AVHWDeviceType hwaccel_device_type;
|
||||
char *hwaccel_device;
|
||||
enum AVPixelFormat hwaccel_output_format;
|
||||
|
||||
int (*hwaccel_retrieve_data)(AVCodecContext *s, AVFrame *frame);
|
||||
enum AVPixelFormat hwaccel_pix_fmt;
|
||||
|
||||
/* stats */
|
||||
// combined size of all the packets read
|
||||
uint64_t data_size;
|
||||
/* number of packets successfully read for this stream */
|
||||
uint64_t nb_packets;
|
||||
// number of frames/samples retrieved from the decoder
|
||||
uint64_t frames_decoded;
|
||||
uint64_t samples_decoded;
|
||||
|
||||
int64_t *dts_buffer;
|
||||
int nb_dts_buffer;
|
||||
|
||||
int got_output;
|
||||
} InputStream;
|
||||
|
||||
typedef struct LastFrameDuration {
|
||||
int stream_idx;
|
||||
int64_t duration;
|
||||
} LastFrameDuration;
|
||||
|
||||
typedef struct InputFile {
|
||||
int index;
|
||||
|
||||
AVFormatContext *ctx;
|
||||
int eof_reached; /* true if eof reached */
|
||||
int eagain; /* true if last read attempt returned EAGAIN */
|
||||
int64_t input_ts_offset;
|
||||
int input_sync_ref;
|
||||
/**
|
||||
* Effective format start time based on enabled streams.
|
||||
*/
|
||||
int64_t start_time_effective;
|
||||
int64_t ts_offset;
|
||||
/**
|
||||
* Extra timestamp offset added by discontinuity handling.
|
||||
*/
|
||||
int64_t ts_offset_discont;
|
||||
int64_t last_ts;
|
||||
int64_t start_time; /* user-specified start time in AV_TIME_BASE or AV_NOPTS_VALUE */
|
||||
int64_t recording_time;
|
||||
|
||||
/* streams that ffmpeg is aware of;
|
||||
* there may be extra streams in ctx that are not mapped to an InputStream
|
||||
* if new streams appear dynamically during demuxing */
|
||||
InputStream **streams;
|
||||
int nb_streams;
|
||||
|
||||
int rate_emu;
|
||||
float readrate;
|
||||
int accurate_seek;
|
||||
|
||||
/* when looping the input file, this queue is used by decoders to report
|
||||
* the last frame duration back to the demuxer thread */
|
||||
AVThreadMessageQueue *audio_duration_queue;
|
||||
int audio_duration_queue_size;
|
||||
} InputFile;
|
||||
|
||||
enum forced_keyframes_const {
|
||||
FKF_N,
|
||||
FKF_N_FORCED,
|
||||
FKF_PREV_FORCED_N,
|
||||
FKF_PREV_FORCED_T,
|
||||
FKF_T,
|
||||
FKF_NB
|
||||
};
|
||||
|
||||
#define ABORT_ON_FLAG_EMPTY_OUTPUT (1 << 0)
|
||||
#define ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM (1 << 1)
|
||||
|
||||
enum EncStatsType {
|
||||
ENC_STATS_LITERAL = 0,
|
||||
ENC_STATS_FILE_IDX,
|
||||
ENC_STATS_STREAM_IDX,
|
||||
ENC_STATS_FRAME_NUM,
|
||||
ENC_STATS_FRAME_NUM_IN,
|
||||
ENC_STATS_TIMEBASE,
|
||||
ENC_STATS_TIMEBASE_IN,
|
||||
ENC_STATS_PTS,
|
||||
ENC_STATS_PTS_TIME,
|
||||
ENC_STATS_PTS_IN,
|
||||
ENC_STATS_PTS_TIME_IN,
|
||||
ENC_STATS_DTS,
|
||||
ENC_STATS_DTS_TIME,
|
||||
ENC_STATS_SAMPLE_NUM,
|
||||
ENC_STATS_NB_SAMPLES,
|
||||
ENC_STATS_PKT_SIZE,
|
||||
ENC_STATS_BITRATE,
|
||||
ENC_STATS_AVG_BITRATE,
|
||||
};
|
||||
|
||||
typedef struct EncStatsComponent {
|
||||
enum EncStatsType type;
|
||||
|
||||
uint8_t *str;
|
||||
size_t str_len;
|
||||
} EncStatsComponent;
|
||||
|
||||
typedef struct EncStats {
|
||||
EncStatsComponent *components;
|
||||
int nb_components;
|
||||
|
||||
AVIOContext *io;
|
||||
} EncStats;
|
||||
|
||||
extern const char *const forced_keyframes_const_names[];
|
||||
|
||||
typedef enum {
|
||||
ENCODER_FINISHED = 1,
|
||||
MUXER_FINISHED = 2,
|
||||
} OSTFinished ;
|
||||
|
||||
enum {
|
||||
KF_FORCE_SOURCE = 1,
|
||||
KF_FORCE_SOURCE_NO_DROP = 2,
|
||||
};
|
||||
|
||||
typedef struct KeyframeForceCtx {
|
||||
int type;
|
||||
|
||||
int64_t ref_pts;
|
||||
|
||||
// timestamps of the forced keyframes, in AV_TIME_BASE_Q
|
||||
int64_t *pts;
|
||||
int nb_pts;
|
||||
int index;
|
||||
|
||||
AVExpr *pexpr;
|
||||
double expr_const_values[FKF_NB];
|
||||
|
||||
int dropped_keyframe;
|
||||
} KeyframeForceCtx;
|
||||
|
||||
typedef struct OutputStream {
|
||||
const AVClass *clazz;
|
||||
|
||||
int file_index; /* file index */
|
||||
int index; /* stream index in the output file */
|
||||
|
||||
/* input stream that is the source for this output stream;
|
||||
* may be NULL for streams with no well-defined source, e.g.
|
||||
* attachments or outputs from complex filtergraphs */
|
||||
InputStream *ist;
|
||||
|
||||
AVStream *st; /* stream in the output file */
|
||||
/* number of frames emitted by the video-encoding sync code */
|
||||
int64_t vsync_frame_number;
|
||||
/* predicted pts of the next frame to be encoded
|
||||
* audio/video encoding only */
|
||||
int64_t next_pts;
|
||||
/* dts of the last packet sent to the muxing queue, in AV_TIME_BASE_Q */
|
||||
int64_t last_mux_dts;
|
||||
/* pts of the last frame received from the filters, in AV_TIME_BASE_Q */
|
||||
int64_t last_filter_pts;
|
||||
|
||||
// timestamp from which the streamcopied streams should start,
|
||||
// in AV_TIME_BASE_Q;
|
||||
// everything before it should be discarded
|
||||
int64_t ts_copy_start;
|
||||
|
||||
// the timebase of the packets sent to the muxer
|
||||
AVRational mux_timebase;
|
||||
AVRational enc_timebase;
|
||||
|
||||
AVCodecContext *enc_ctx;
|
||||
AVFrame *filtered_frame;
|
||||
AVFrame *last_frame;
|
||||
AVFrame *sq_frame;
|
||||
AVPacket *pkt;
|
||||
int64_t last_dropped;
|
||||
int64_t last_nb0_frames[3];
|
||||
|
||||
/* video only */
|
||||
AVRational frame_rate;
|
||||
AVRational max_frame_rate;
|
||||
enum VideoSyncMethod vsync_method;
|
||||
int is_cfr;
|
||||
int force_fps;
|
||||
int top_field_first;
|
||||
#if FFMPEG_ROTATION_METADATA
|
||||
int rotate_overridden;
|
||||
#endif
|
||||
int autoscale;
|
||||
int bitexact;
|
||||
int bits_per_raw_sample;
|
||||
#if FFMPEG_ROTATION_METADATA
|
||||
double rotate_override_value;
|
||||
#endif
|
||||
|
||||
AVRational frame_aspect_ratio;
|
||||
|
||||
KeyframeForceCtx kf;
|
||||
|
||||
/* audio only */
|
||||
#if FFMPEG_OPT_MAP_CHANNEL
|
||||
int *audio_channels_map; /* list of the channels id to pick from the source stream */
|
||||
int audio_channels_mapped; /* number of channels in audio_channels_map */
|
||||
#endif
|
||||
|
||||
char *logfile_prefix;
|
||||
FILE *logfile;
|
||||
|
||||
OutputFilter *filter;
|
||||
char *avfilter;
|
||||
char *filters; ///< filtergraph associated to the -filter option
|
||||
char *filters_script; ///< filtergraph script associated to the -filter_script option
|
||||
|
||||
AVDictionary *encoder_opts;
|
||||
AVDictionary *sws_dict;
|
||||
AVDictionary *swr_opts;
|
||||
char *apad;
|
||||
OSTFinished finished; /* no more packets should be written for this stream */
|
||||
int unavailable; /* true if the steram is unavailable (possibly temporarily) */
|
||||
|
||||
// init_output_stream() has been called for this stream
|
||||
// The encoder and the bitstream filters have been initialized and the stream
|
||||
// parameters are set in the AVStream.
|
||||
int initialized;
|
||||
|
||||
int inputs_done;
|
||||
|
||||
const char *attachment_filename;
|
||||
int streamcopy_started;
|
||||
int copy_initial_nonkeyframes;
|
||||
int copy_prior_start;
|
||||
|
||||
int keep_pix_fmt;
|
||||
|
||||
/* stats */
|
||||
// combined size of all the packets sent to the muxer
|
||||
uint64_t data_size_mux;
|
||||
// combined size of all the packets received from the encoder
|
||||
uint64_t data_size_enc;
|
||||
// number of packets send to the muxer
|
||||
atomic_uint_least64_t packets_written;
|
||||
// number of frames/samples sent to the encoder
|
||||
uint64_t frames_encoded;
|
||||
uint64_t samples_encoded;
|
||||
// number of packets received from the encoder
|
||||
uint64_t packets_encoded;
|
||||
|
||||
/* packet quality factor */
|
||||
int quality;
|
||||
|
||||
/* packet picture type */
|
||||
int pict_type;
|
||||
|
||||
/* frame encode sum of squared error values */
|
||||
int64_t error[4];
|
||||
|
||||
int sq_idx_encode;
|
||||
int sq_idx_mux;
|
||||
|
||||
EncStats enc_stats_pre;
|
||||
EncStats enc_stats_post;
|
||||
|
||||
/*
|
||||
* bool on whether this stream should be utilized for splitting
|
||||
* subtitles utilizing fix_sub_duration at random access points.
|
||||
*/
|
||||
unsigned int fix_sub_duration_heartbeat;
|
||||
} OutputStream;
|
||||
|
||||
typedef struct OutputFile {
|
||||
const AVClass *clazz;
|
||||
|
||||
int index;
|
||||
|
||||
const AVOutputFormat *format;
|
||||
const char *url;
|
||||
|
||||
OutputStream **streams;
|
||||
int nb_streams;
|
||||
|
||||
SyncQueue *sq_encode;
|
||||
|
||||
int64_t recording_time; ///< desired length of the resulting file in microseconds == AV_TIME_BASE units
|
||||
int64_t start_time; ///< start time in microseconds == AV_TIME_BASE units
|
||||
|
||||
int shortest;
|
||||
int bitexact;
|
||||
} OutputFile;
|
||||
|
||||
extern __thread InputFile **input_files;
|
||||
extern __thread int nb_input_files;
|
||||
|
||||
extern __thread OutputFile **output_files;
|
||||
extern __thread int nb_output_files;
|
||||
|
||||
extern __thread FilterGraph **filtergraphs;
|
||||
extern __thread int nb_filtergraphs;
|
||||
|
||||
extern __thread char *vstats_filename;
|
||||
extern __thread char *sdp_filename;
|
||||
|
||||
extern __thread float audio_drift_threshold;
|
||||
extern __thread float dts_delta_threshold;
|
||||
extern __thread float dts_error_threshold;
|
||||
|
||||
extern __thread enum VideoSyncMethod video_sync_method;
|
||||
extern __thread float frame_drop_threshold;
|
||||
extern __thread int do_benchmark;
|
||||
extern __thread int do_benchmark_all;
|
||||
extern __thread int do_hex_dump;
|
||||
extern __thread int do_pkt_dump;
|
||||
extern __thread int copy_ts;
|
||||
extern __thread int start_at_zero;
|
||||
extern __thread int copy_tb;
|
||||
extern __thread int debug_ts;
|
||||
extern __thread int exit_on_error;
|
||||
extern __thread int abort_on_flags;
|
||||
extern __thread int print_stats;
|
||||
extern __thread int64_t stats_period;
|
||||
extern __thread int qp_hist;
|
||||
extern __thread int stdin_interaction;
|
||||
extern __thread AVIOContext *progress_avio;
|
||||
extern __thread float max_error_rate;
|
||||
|
||||
extern __thread char *filter_nbthreads;
|
||||
extern __thread int filter_complex_nbthreads;
|
||||
extern __thread int vstats_version;
|
||||
extern __thread int auto_conversion_filters;
|
||||
|
||||
extern __thread const AVIOInterruptCB int_cb;
|
||||
|
||||
extern __thread HWDevice *filter_hw_device;
|
||||
|
||||
extern __thread unsigned nb_output_dumped;
|
||||
extern __thread int main_ffmpeg_return_code;
|
||||
|
||||
extern __thread int ignore_unknown_streams;
|
||||
extern __thread int copy_unknown_streams;
|
||||
|
||||
extern __thread int recast_media;
|
||||
|
||||
#if FFMPEG_OPT_PSNR
|
||||
extern __thread int do_psnr;
|
||||
#endif
|
||||
|
||||
void term_init(void);
|
||||
void term_exit(void);
|
||||
|
||||
void show_usage(void);
|
||||
|
||||
void remove_avoptions(AVDictionary **a, AVDictionary *b);
|
||||
void assert_avoptions(AVDictionary *m);
|
||||
|
||||
void assert_file_overwrite(const char *filename);
|
||||
char *file_read(const char *filename);
|
||||
AVDictionary *strip_specifiers(const AVDictionary *dict);
|
||||
const AVCodec *find_codec_or_die(void *logctx, const char *name,
|
||||
enum AVMediaType type, int encoder);
|
||||
int parse_and_set_vsync(const char *arg, int *vsync_var, int file_idx, int st_idx, int is_global);
|
||||
|
||||
int configure_filtergraph(FilterGraph *fg);
|
||||
void check_filter_outputs(void);
|
||||
int filtergraph_is_simple(FilterGraph *fg);
|
||||
int init_simple_filtergraph(InputStream *ist, OutputStream *ost);
|
||||
int init_complex_filtergraph(FilterGraph *fg);
|
||||
|
||||
void sub2video_update(InputStream *ist, int64_t heartbeat_pts, AVSubtitle *sub);
|
||||
|
||||
int ifilter_parameters_from_frame(InputFilter *ifilter, const AVFrame *frame);
|
||||
|
||||
int ffmpeg_parse_options(int argc, char **argv);
|
||||
|
||||
void enc_stats_write(OutputStream *ost, EncStats *es,
|
||||
const AVFrame *frame, const AVPacket *pkt,
|
||||
uint64_t frame_num);
|
||||
|
||||
HWDevice *hw_device_get_by_name(const char *name);
|
||||
int hw_device_init_from_string(const char *arg, HWDevice **dev);
|
||||
void hw_device_free_all(void);
|
||||
|
||||
int hw_device_setup_for_decode(InputStream *ist);
|
||||
int hw_device_setup_for_encode(OutputStream *ost);
|
||||
int hw_device_setup_for_filter(FilterGraph *fg);
|
||||
|
||||
int hwaccel_decode_init(AVCodecContext *avctx);
|
||||
|
||||
/*
|
||||
* Initialize muxing state for the given stream, should be called
|
||||
* after the codec/streamcopy setup has been done.
|
||||
*
|
||||
* Open the muxer once all the streams have been initialized.
|
||||
*/
|
||||
int of_stream_init(OutputFile *of, OutputStream *ost);
|
||||
int of_write_trailer(OutputFile *of);
|
||||
int of_open(const OptionsContext *o, const char *filename);
|
||||
void of_close(OutputFile **pof);
|
||||
|
||||
void of_enc_stats_close(void);
|
||||
|
||||
/*
|
||||
* Send a single packet to the output, applying any bitstream filters
|
||||
* associated with the output stream. This may result in any number
|
||||
* of packets actually being written, depending on what bitstream
|
||||
* filters are applied. The supplied packet is consumed and will be
|
||||
* blank (as if newly-allocated) when this function returns.
|
||||
*
|
||||
* If eof is set, instead indicate EOF to all bitstream filters and
|
||||
* therefore flush any delayed packets to the output. A blank packet
|
||||
* must be supplied in this case.
|
||||
*/
|
||||
void of_output_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, int eof);
|
||||
int64_t of_filesize(OutputFile *of);
|
||||
|
||||
int ifile_open(const OptionsContext *o, const char *filename);
|
||||
void ifile_close(InputFile **f);
|
||||
|
||||
/**
|
||||
* Get next input packet from the demuxer.
|
||||
*
|
||||
* @param pkt the packet is written here when this function returns 0
|
||||
* @return
|
||||
* - 0 when a packet has been read successfully
|
||||
* - 1 when stream end was reached, but the stream is looped;
|
||||
* caller should flush decoders and read from this demuxer again
|
||||
* - a negative error code on failure
|
||||
*/
|
||||
int ifile_get_packet(InputFile *f, AVPacket **pkt);
|
||||
|
||||
/* iterate over all input streams in all input files;
|
||||
* pass NULL to start iteration */
|
||||
InputStream *ist_iter(InputStream *prev);
|
||||
|
||||
extern const char * const opt_name_codec_names[];
|
||||
extern const char * const opt_name_codec_tags[];
|
||||
extern const char * const opt_name_frame_rates[];
|
||||
extern const char * const opt_name_top_field_first[];
|
||||
|
||||
void set_report_callback(void (*callback)(int, float, float, int64_t, double, double, double));
|
||||
void cancel_operation(long id);
|
||||
|
||||
#endif /* FFTOOLS_FFMPEG_H */
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,608 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019 Taner Sener
|
||||
* Copyright (c) 2023 ARTHENICA LTD
|
||||
*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is the modified version of ffmpeg_hw.c file living in ffmpeg source code under the fftools folder. We
|
||||
* manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied
|
||||
* by us to develop mobile-ffmpeg and later ffmpeg-kit libraries.
|
||||
*
|
||||
* ffmpeg-kit changes by ARTHENICA LTD
|
||||
*
|
||||
* 07.2023
|
||||
* --------------------------------------------------------
|
||||
* - FFmpeg 6.0 changes migrated
|
||||
*
|
||||
* mobile-ffmpeg / ffmpeg-kit changes by Taner Sener
|
||||
*
|
||||
* 12.2019
|
||||
* --------------------------------------------------------
|
||||
* - concurrent execution support ("__thread" specifier added to variables used by multiple threads)
|
||||
*
|
||||
* 08.2018
|
||||
* --------------------------------------------------------
|
||||
* - fftools_ prefix added to file name and parent header
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/pixdesc.h"
|
||||
#include "libavfilter/buffersink.h"
|
||||
|
||||
#include "fftools_ffmpeg.h"
|
||||
|
||||
__thread int nb_hw_devices;
|
||||
__thread HWDevice **hw_devices;
|
||||
|
||||
static HWDevice *hw_device_get_by_type(enum AVHWDeviceType type)
|
||||
{
|
||||
HWDevice *found = NULL;
|
||||
int i;
|
||||
for (i = 0; i < nb_hw_devices; i++) {
|
||||
if (hw_devices[i]->type == type) {
|
||||
if (found)
|
||||
return NULL;
|
||||
found = hw_devices[i];
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
HWDevice *hw_device_get_by_name(const char *name)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < nb_hw_devices; i++) {
|
||||
if (!strcmp(hw_devices[i]->name, name))
|
||||
return hw_devices[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static HWDevice *hw_device_add(void)
|
||||
{
|
||||
int err;
|
||||
err = av_reallocp_array(&hw_devices, nb_hw_devices + 1,
|
||||
sizeof(*hw_devices));
|
||||
if (err) {
|
||||
nb_hw_devices = 0;
|
||||
return NULL;
|
||||
}
|
||||
hw_devices[nb_hw_devices] = av_mallocz(sizeof(HWDevice));
|
||||
if (!hw_devices[nb_hw_devices])
|
||||
return NULL;
|
||||
return hw_devices[nb_hw_devices++];
|
||||
}
|
||||
|
||||
static char *hw_device_default_name(enum AVHWDeviceType type)
|
||||
{
|
||||
// Make an automatic name of the form "type%d". We arbitrarily
|
||||
// limit at 1000 anonymous devices of the same type - there is
|
||||
// probably something else very wrong if you get to this limit.
|
||||
const char *type_name = av_hwdevice_get_type_name(type);
|
||||
char *name;
|
||||
size_t index_pos;
|
||||
int index, index_limit = 1000;
|
||||
index_pos = strlen(type_name);
|
||||
name = av_malloc(index_pos + 4);
|
||||
if (!name)
|
||||
return NULL;
|
||||
for (index = 0; index < index_limit; index++) {
|
||||
snprintf(name, index_pos + 4, "%s%d", type_name, index);
|
||||
if (!hw_device_get_by_name(name))
|
||||
break;
|
||||
}
|
||||
if (index >= index_limit) {
|
||||
av_freep(&name);
|
||||
return NULL;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
int hw_device_init_from_string(const char *arg, HWDevice **dev_out)
|
||||
{
|
||||
// "type=name"
|
||||
// "type=name,key=value,key2=value2"
|
||||
// "type=name:device,key=value,key2=value2"
|
||||
// "type:device,key=value,key2=value2"
|
||||
// -> av_hwdevice_ctx_create()
|
||||
// "type=name@name"
|
||||
// "type@name"
|
||||
// -> av_hwdevice_ctx_create_derived()
|
||||
|
||||
AVDictionary *options = NULL;
|
||||
const char *type_name = NULL, *name = NULL, *device = NULL;
|
||||
enum AVHWDeviceType type;
|
||||
HWDevice *dev, *src;
|
||||
AVBufferRef *device_ref = NULL;
|
||||
int err;
|
||||
const char *errmsg, *p, *q;
|
||||
size_t k;
|
||||
|
||||
k = strcspn(arg, ":=@");
|
||||
p = arg + k;
|
||||
|
||||
type_name = av_strndup(arg, k);
|
||||
if (!type_name) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
type = av_hwdevice_find_type_by_name(type_name);
|
||||
if (type == AV_HWDEVICE_TYPE_NONE) {
|
||||
errmsg = "unknown device type";
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
if (*p == '=') {
|
||||
k = strcspn(p + 1, ":@,");
|
||||
|
||||
name = av_strndup(p + 1, k);
|
||||
if (!name) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
if (hw_device_get_by_name(name)) {
|
||||
errmsg = "named device already exists";
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
p += 1 + k;
|
||||
} else {
|
||||
name = hw_device_default_name(type);
|
||||
if (!name) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (!*p) {
|
||||
// New device with no parameters.
|
||||
err = av_hwdevice_ctx_create(&device_ref, type,
|
||||
NULL, NULL, 0);
|
||||
if (err < 0)
|
||||
goto fail;
|
||||
|
||||
} else if (*p == ':') {
|
||||
// New device with some parameters.
|
||||
++p;
|
||||
q = strchr(p, ',');
|
||||
if (q) {
|
||||
if (q - p > 0) {
|
||||
device = av_strndup(p, q - p);
|
||||
if (!device) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
err = av_dict_parse_string(&options, q + 1, "=", ",", 0);
|
||||
if (err < 0) {
|
||||
errmsg = "failed to parse options";
|
||||
goto invalid;
|
||||
}
|
||||
}
|
||||
|
||||
err = av_hwdevice_ctx_create(&device_ref, type,
|
||||
q ? device : p[0] ? p : NULL,
|
||||
options, 0);
|
||||
if (err < 0)
|
||||
goto fail;
|
||||
|
||||
} else if (*p == '@') {
|
||||
// Derive from existing device.
|
||||
|
||||
src = hw_device_get_by_name(p + 1);
|
||||
if (!src) {
|
||||
errmsg = "invalid source device name";
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
err = av_hwdevice_ctx_create_derived(&device_ref, type,
|
||||
src->device_ref, 0);
|
||||
if (err < 0)
|
||||
goto fail;
|
||||
} else if (*p == ',') {
|
||||
err = av_dict_parse_string(&options, p + 1, "=", ",", 0);
|
||||
|
||||
if (err < 0) {
|
||||
errmsg = "failed to parse options";
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
err = av_hwdevice_ctx_create(&device_ref, type,
|
||||
NULL, options, 0);
|
||||
if (err < 0)
|
||||
goto fail;
|
||||
} else {
|
||||
errmsg = "parse error";
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
dev = hw_device_add();
|
||||
if (!dev) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dev->name = name;
|
||||
dev->type = type;
|
||||
dev->device_ref = device_ref;
|
||||
|
||||
if (dev_out)
|
||||
*dev_out = dev;
|
||||
|
||||
name = NULL;
|
||||
err = 0;
|
||||
done:
|
||||
av_freep(&type_name);
|
||||
av_freep(&name);
|
||||
av_freep(&device);
|
||||
av_dict_free(&options);
|
||||
return err;
|
||||
invalid:
|
||||
av_log(NULL, AV_LOG_ERROR,
|
||||
"Invalid device specification \"%s\": %s\n", arg, errmsg);
|
||||
err = AVERROR(EINVAL);
|
||||
goto done;
|
||||
fail:
|
||||
av_log(NULL, AV_LOG_ERROR,
|
||||
"Device creation failed: %d.\n", err);
|
||||
av_buffer_unref(&device_ref);
|
||||
goto done;
|
||||
}
|
||||
|
||||
static int hw_device_init_from_type(enum AVHWDeviceType type,
|
||||
const char *device,
|
||||
HWDevice **dev_out)
|
||||
{
|
||||
AVBufferRef *device_ref = NULL;
|
||||
HWDevice *dev;
|
||||
char *name;
|
||||
int err;
|
||||
|
||||
name = hw_device_default_name(type);
|
||||
if (!name) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = av_hwdevice_ctx_create(&device_ref, type, device, NULL, 0);
|
||||
if (err < 0) {
|
||||
av_log(NULL, AV_LOG_ERROR,
|
||||
"Device creation failed: %d.\n", err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dev = hw_device_add();
|
||||
if (!dev) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dev->name = name;
|
||||
dev->type = type;
|
||||
dev->device_ref = device_ref;
|
||||
|
||||
if (dev_out)
|
||||
*dev_out = dev;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
av_freep(&name);
|
||||
av_buffer_unref(&device_ref);
|
||||
return err;
|
||||
}
|
||||
|
||||
void hw_device_free_all(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < nb_hw_devices; i++) {
|
||||
av_freep(&hw_devices[i]->name);
|
||||
av_buffer_unref(&hw_devices[i]->device_ref);
|
||||
av_freep(&hw_devices[i]);
|
||||
}
|
||||
av_freep(&hw_devices);
|
||||
nb_hw_devices = 0;
|
||||
}
|
||||
|
||||
static HWDevice *hw_device_match_by_codec(const AVCodec *codec)
|
||||
{
|
||||
const AVCodecHWConfig *config;
|
||||
HWDevice *dev;
|
||||
int i;
|
||||
for (i = 0;; i++) {
|
||||
config = avcodec_get_hw_config(codec, i);
|
||||
if (!config)
|
||||
return NULL;
|
||||
if (!(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX))
|
||||
continue;
|
||||
dev = hw_device_get_by_type(config->device_type);
|
||||
if (dev)
|
||||
return dev;
|
||||
}
|
||||
}
|
||||
|
||||
int hw_device_setup_for_decode(InputStream *ist)
|
||||
{
|
||||
const AVCodecHWConfig *config;
|
||||
enum AVHWDeviceType type;
|
||||
HWDevice *dev = NULL;
|
||||
int err, auto_device = 0;
|
||||
|
||||
if (ist->hwaccel_device) {
|
||||
dev = hw_device_get_by_name(ist->hwaccel_device);
|
||||
if (!dev) {
|
||||
if (ist->hwaccel_id == HWACCEL_AUTO) {
|
||||
auto_device = 1;
|
||||
} else if (ist->hwaccel_id == HWACCEL_GENERIC) {
|
||||
type = ist->hwaccel_device_type;
|
||||
err = hw_device_init_from_type(type, ist->hwaccel_device,
|
||||
&dev);
|
||||
} else {
|
||||
// This will be dealt with by API-specific initialisation
|
||||
// (using hwaccel_device), so nothing further needed here.
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (ist->hwaccel_id == HWACCEL_AUTO) {
|
||||
ist->hwaccel_device_type = dev->type;
|
||||
} else if (ist->hwaccel_device_type != dev->type) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Invalid hwaccel device "
|
||||
"specified for decoder: device %s of type %s is not "
|
||||
"usable with hwaccel %s.\n", dev->name,
|
||||
av_hwdevice_get_type_name(dev->type),
|
||||
av_hwdevice_get_type_name(ist->hwaccel_device_type));
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (ist->hwaccel_id == HWACCEL_AUTO) {
|
||||
auto_device = 1;
|
||||
} else if (ist->hwaccel_id == HWACCEL_GENERIC) {
|
||||
type = ist->hwaccel_device_type;
|
||||
dev = hw_device_get_by_type(type);
|
||||
|
||||
// When "-qsv_device device" is used, an internal QSV device named
|
||||
// as "__qsv_device" is created. Another QSV device is created too
|
||||
// if "-init_hw_device qsv=name:device" is used. There are 2 QSV devices
|
||||
// if both "-qsv_device device" and "-init_hw_device qsv=name:device"
|
||||
// are used, hw_device_get_by_type(AV_HWDEVICE_TYPE_QSV) returns NULL.
|
||||
// To keep back-compatibility with the removed ad-hoc libmfx setup code,
|
||||
// call hw_device_get_by_name("__qsv_device") to select the internal QSV
|
||||
// device.
|
||||
if (!dev && type == AV_HWDEVICE_TYPE_QSV)
|
||||
dev = hw_device_get_by_name("__qsv_device");
|
||||
|
||||
if (!dev)
|
||||
err = hw_device_init_from_type(type, NULL, &dev);
|
||||
} else {
|
||||
dev = hw_device_match_by_codec(ist->dec);
|
||||
if (!dev) {
|
||||
// No device for this codec, but not using generic hwaccel
|
||||
// and therefore may well not need one - ignore.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto_device) {
|
||||
int i;
|
||||
if (!avcodec_get_hw_config(ist->dec, 0)) {
|
||||
// Decoder does not support any hardware devices.
|
||||
return 0;
|
||||
}
|
||||
for (i = 0; !dev; i++) {
|
||||
config = avcodec_get_hw_config(ist->dec, i);
|
||||
if (!config)
|
||||
break;
|
||||
type = config->device_type;
|
||||
dev = hw_device_get_by_type(type);
|
||||
if (dev) {
|
||||
av_log(NULL, AV_LOG_INFO, "Using auto "
|
||||
"hwaccel type %s with existing device %s.\n",
|
||||
av_hwdevice_get_type_name(type), dev->name);
|
||||
}
|
||||
}
|
||||
for (i = 0; !dev; i++) {
|
||||
config = avcodec_get_hw_config(ist->dec, i);
|
||||
if (!config)
|
||||
break;
|
||||
type = config->device_type;
|
||||
// Try to make a new device of this type.
|
||||
err = hw_device_init_from_type(type, ist->hwaccel_device,
|
||||
&dev);
|
||||
if (err < 0) {
|
||||
// Can't make a device of this type.
|
||||
continue;
|
||||
}
|
||||
if (ist->hwaccel_device) {
|
||||
av_log(NULL, AV_LOG_INFO, "Using auto "
|
||||
"hwaccel type %s with new device created "
|
||||
"from %s.\n", av_hwdevice_get_type_name(type),
|
||||
ist->hwaccel_device);
|
||||
} else {
|
||||
av_log(NULL, AV_LOG_INFO, "Using auto "
|
||||
"hwaccel type %s with new default device.\n",
|
||||
av_hwdevice_get_type_name(type));
|
||||
}
|
||||
}
|
||||
if (dev) {
|
||||
ist->hwaccel_device_type = type;
|
||||
} else {
|
||||
av_log(NULL, AV_LOG_INFO, "Auto hwaccel "
|
||||
"disabled: no device found.\n");
|
||||
ist->hwaccel_id = HWACCEL_NONE;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dev) {
|
||||
av_log(NULL, AV_LOG_ERROR, "No device available "
|
||||
"for decoder: device type %s needed for codec %s.\n",
|
||||
av_hwdevice_get_type_name(type), ist->dec->name);
|
||||
return err;
|
||||
}
|
||||
|
||||
ist->dec_ctx->hw_device_ctx = av_buffer_ref(dev->device_ref);
|
||||
if (!ist->dec_ctx->hw_device_ctx)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hw_device_setup_for_encode(OutputStream *ost)
|
||||
{
|
||||
const AVCodecHWConfig *config;
|
||||
HWDevice *dev = NULL;
|
||||
AVBufferRef *frames_ref = NULL;
|
||||
int i;
|
||||
|
||||
if (ost->filter) {
|
||||
frames_ref = av_buffersink_get_hw_frames_ctx(ost->filter->filter);
|
||||
if (frames_ref &&
|
||||
((AVHWFramesContext*)frames_ref->data)->format ==
|
||||
ost->enc_ctx->pix_fmt) {
|
||||
// Matching format, will try to use hw_frames_ctx.
|
||||
} else {
|
||||
frames_ref = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0;; i++) {
|
||||
config = avcodec_get_hw_config(ost->enc_ctx->codec, i);
|
||||
if (!config)
|
||||
break;
|
||||
|
||||
if (frames_ref &&
|
||||
config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX &&
|
||||
(config->pix_fmt == AV_PIX_FMT_NONE ||
|
||||
config->pix_fmt == ost->enc_ctx->pix_fmt)) {
|
||||
av_log(ost->enc_ctx, AV_LOG_VERBOSE, "Using input "
|
||||
"frames context (format %s) with %s encoder.\n",
|
||||
av_get_pix_fmt_name(ost->enc_ctx->pix_fmt),
|
||||
ost->enc_ctx->codec->name);
|
||||
ost->enc_ctx->hw_frames_ctx = av_buffer_ref(frames_ref);
|
||||
if (!ost->enc_ctx->hw_frames_ctx)
|
||||
return AVERROR(ENOMEM);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!dev &&
|
||||
config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)
|
||||
dev = hw_device_get_by_type(config->device_type);
|
||||
}
|
||||
|
||||
if (dev) {
|
||||
av_log(ost->enc_ctx, AV_LOG_VERBOSE, "Using device %s "
|
||||
"(type %s) with %s encoder.\n", dev->name,
|
||||
av_hwdevice_get_type_name(dev->type), ost->enc_ctx->codec->name);
|
||||
ost->enc_ctx->hw_device_ctx = av_buffer_ref(dev->device_ref);
|
||||
if (!ost->enc_ctx->hw_device_ctx)
|
||||
return AVERROR(ENOMEM);
|
||||
} else {
|
||||
// No device required, or no device available.
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hwaccel_retrieve_data(AVCodecContext *avctx, AVFrame *input)
|
||||
{
|
||||
InputStream *ist = avctx->opaque;
|
||||
AVFrame *output = NULL;
|
||||
enum AVPixelFormat output_format = ist->hwaccel_output_format;
|
||||
int err;
|
||||
|
||||
if (input->format == output_format) {
|
||||
// Nothing to do.
|
||||
return 0;
|
||||
}
|
||||
|
||||
output = av_frame_alloc();
|
||||
if (!output)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
output->format = output_format;
|
||||
|
||||
err = av_hwframe_transfer_data(output, input, 0);
|
||||
if (err < 0) {
|
||||
av_log(avctx, AV_LOG_ERROR, "Failed to transfer data to "
|
||||
"output frame: %d.\n", err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = av_frame_copy_props(output, input);
|
||||
if (err < 0) {
|
||||
av_frame_unref(output);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
av_frame_unref(input);
|
||||
av_frame_move_ref(input, output);
|
||||
av_frame_free(&output);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
av_frame_free(&output);
|
||||
return err;
|
||||
}
|
||||
|
||||
int hwaccel_decode_init(AVCodecContext *avctx)
|
||||
{
|
||||
InputStream *ist = avctx->opaque;
|
||||
|
||||
ist->hwaccel_retrieve_data = &hwaccel_retrieve_data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hw_device_setup_for_filter(FilterGraph *fg)
|
||||
{
|
||||
HWDevice *dev;
|
||||
int i;
|
||||
|
||||
// Pick the last hardware device if the user doesn't pick the device for
|
||||
// filters explicitly with the filter_hw_device option.
|
||||
if (filter_hw_device)
|
||||
dev = filter_hw_device;
|
||||
else if (nb_hw_devices > 0) {
|
||||
dev = hw_devices[nb_hw_devices - 1];
|
||||
|
||||
if (nb_hw_devices > 1)
|
||||
av_log(NULL, AV_LOG_WARNING, "There are %d hardware devices. device "
|
||||
"%s of type %s is picked for filters by default. Set hardware "
|
||||
"device explicitly with the filter_hw_device option if device "
|
||||
"%s is not usable for filters.\n",
|
||||
nb_hw_devices, dev->name,
|
||||
av_hwdevice_get_type_name(dev->type), dev->name);
|
||||
} else
|
||||
dev = NULL;
|
||||
|
||||
if (dev) {
|
||||
for (i = 0; i < fg->graph->nb_filters; i++) {
|
||||
fg->graph->filters[i]->hw_device_ctx =
|
||||
av_buffer_ref(dev->device_ref);
|
||||
if (!fg->graph->filters[i]->hw_device_ctx)
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,781 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
* Copyright (c) 2022 Taner Sener
|
||||
* Copyright (c) 2023 ARTHENICA LTD
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is the modified version of ffmpeg_mux.c file living in ffmpeg source code under the fftools folder. We
|
||||
* manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied
|
||||
* by us to develop the ffmpeg-kit library.
|
||||
*
|
||||
* ffmpeg-kit changes by ARTHENICA LTD
|
||||
*
|
||||
* 07.2023
|
||||
* --------------------------------------------------------
|
||||
* - FFmpeg 6.0 changes migrated
|
||||
* - fftools header names updated
|
||||
* - want_sdp marked as thread-local
|
||||
* - ms_from_ost migrated from ffmpeg_mux.c and marked as non-static
|
||||
*
|
||||
* ffmpeg-kit changes by Taner Sener
|
||||
*
|
||||
* 09.2022
|
||||
* --------------------------------------------------------
|
||||
* - fftools_ prefix added to fftools headers
|
||||
* - using main_ffmpeg_return_code instead of main_return_code
|
||||
* - printf replaced with av_log statements
|
||||
*/
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "fftools_ffmpeg.h"
|
||||
#include "fftools_ffmpeg_mux.h"
|
||||
#include "fftools_objpool.h"
|
||||
#include "fftools_sync_queue.h"
|
||||
#include "fftools_thread_queue.h"
|
||||
|
||||
#include "libavutil/fifo.h"
|
||||
#include "libavutil/intreadwrite.h"
|
||||
#include "libavutil/log.h"
|
||||
#include "libavutil/mem.h"
|
||||
#include "libavutil/timestamp.h"
|
||||
#include "libavutil/thread.h"
|
||||
|
||||
#include "libavcodec/packet.h"
|
||||
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavformat/avio.h"
|
||||
|
||||
__thread int want_sdp = 1;
|
||||
|
||||
MuxStream *ms_from_ost(OutputStream *ost)
|
||||
{
|
||||
return (MuxStream*)ost;
|
||||
}
|
||||
|
||||
static Muxer *mux_from_of(OutputFile *of)
|
||||
{
|
||||
return (Muxer*)of;
|
||||
}
|
||||
|
||||
static int64_t filesize(AVIOContext *pb)
|
||||
{
|
||||
int64_t ret = -1;
|
||||
|
||||
if (pb) {
|
||||
ret = avio_size(pb);
|
||||
if (ret <= 0) // FIXME improve avio_size() so it works with non seekable output too
|
||||
ret = avio_tell(pb);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int write_packet(Muxer *mux, OutputStream *ost, AVPacket *pkt)
|
||||
{
|
||||
MuxStream *ms = ms_from_ost(ost);
|
||||
AVFormatContext *s = mux->fc;
|
||||
AVStream *st = ost->st;
|
||||
int64_t fs;
|
||||
uint64_t frame_num;
|
||||
int ret;
|
||||
|
||||
fs = filesize(s->pb);
|
||||
atomic_store(&mux->last_filesize, fs);
|
||||
if (fs >= mux->limit_filesize) {
|
||||
ret = AVERROR_EOF;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && ost->vsync_method == VSYNC_DROP)
|
||||
pkt->pts = pkt->dts = AV_NOPTS_VALUE;
|
||||
|
||||
if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
if (ost->frame_rate.num && ost->is_cfr) {
|
||||
if (pkt->duration > 0)
|
||||
av_log(ost, AV_LOG_WARNING, "Overriding packet duration by frame rate, this should not happen\n");
|
||||
pkt->duration = av_rescale_q(1, av_inv_q(ost->frame_rate),
|
||||
pkt->time_base);
|
||||
}
|
||||
}
|
||||
|
||||
av_packet_rescale_ts(pkt, pkt->time_base, ost->st->time_base);
|
||||
pkt->time_base = ost->st->time_base;
|
||||
|
||||
if (!(s->oformat->flags & AVFMT_NOTIMESTAMPS)) {
|
||||
if (pkt->dts != AV_NOPTS_VALUE &&
|
||||
pkt->pts != AV_NOPTS_VALUE &&
|
||||
pkt->dts > pkt->pts) {
|
||||
av_log(s, AV_LOG_WARNING, "Invalid DTS: %"PRId64" PTS: %"PRId64" in output stream %d:%d, replacing by guess\n",
|
||||
pkt->dts, pkt->pts,
|
||||
ost->file_index, ost->st->index);
|
||||
pkt->pts =
|
||||
pkt->dts = pkt->pts + pkt->dts + ms->last_mux_dts + 1
|
||||
- FFMIN3(pkt->pts, pkt->dts, ms->last_mux_dts + 1)
|
||||
- FFMAX3(pkt->pts, pkt->dts, ms->last_mux_dts + 1);
|
||||
}
|
||||
if ((st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) &&
|
||||
pkt->dts != AV_NOPTS_VALUE &&
|
||||
ms->last_mux_dts != AV_NOPTS_VALUE) {
|
||||
int64_t max = ms->last_mux_dts + !(s->oformat->flags & AVFMT_TS_NONSTRICT);
|
||||
if (pkt->dts < max) {
|
||||
int loglevel = max - pkt->dts > 2 || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? AV_LOG_WARNING : AV_LOG_DEBUG;
|
||||
if (exit_on_error)
|
||||
loglevel = AV_LOG_ERROR;
|
||||
av_log(s, loglevel, "Non-monotonous DTS in output stream "
|
||||
"%d:%d; previous: %"PRId64", current: %"PRId64"; ",
|
||||
ost->file_index, ost->st->index, ms->last_mux_dts, pkt->dts);
|
||||
if (exit_on_error) {
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
av_log(s, loglevel, "changing to %"PRId64". This may result "
|
||||
"in incorrect timestamps in the output file.\n",
|
||||
max);
|
||||
if (pkt->pts >= pkt->dts)
|
||||
pkt->pts = FFMAX(pkt->pts, max);
|
||||
pkt->dts = max;
|
||||
}
|
||||
}
|
||||
}
|
||||
ms->last_mux_dts = pkt->dts;
|
||||
|
||||
ost->data_size_mux += pkt->size;
|
||||
frame_num = atomic_fetch_add(&ost->packets_written, 1);
|
||||
|
||||
pkt->stream_index = ost->index;
|
||||
|
||||
if (debug_ts) {
|
||||
av_log(ost, AV_LOG_INFO, "muxer <- type:%s "
|
||||
"pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s size:%d\n",
|
||||
av_get_media_type_string(st->codecpar->codec_type),
|
||||
av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ost->st->time_base),
|
||||
av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ost->st->time_base),
|
||||
av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &ost->st->time_base),
|
||||
pkt->size
|
||||
);
|
||||
}
|
||||
|
||||
if (ms->stats.io)
|
||||
enc_stats_write(ost, &ms->stats, NULL, pkt, frame_num);
|
||||
|
||||
ret = av_interleaved_write_frame(s, pkt);
|
||||
if (ret < 0) {
|
||||
print_error("av_interleaved_write_frame()", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
fail:
|
||||
av_packet_unref(pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sync_queue_process(Muxer *mux, OutputStream *ost, AVPacket *pkt, int *stream_eof)
|
||||
{
|
||||
OutputFile *of = &mux->of;
|
||||
|
||||
if (ost->sq_idx_mux >= 0) {
|
||||
int ret = sq_send(mux->sq_mux, ost->sq_idx_mux, SQPKT(pkt));
|
||||
if (ret < 0) {
|
||||
if (ret == AVERROR_EOF)
|
||||
*stream_eof = 1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
ret = sq_receive(mux->sq_mux, -1, SQPKT(mux->sq_pkt));
|
||||
if (ret < 0)
|
||||
return (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) ? 0 : ret;
|
||||
|
||||
ret = write_packet(mux, of->streams[ret],
|
||||
mux->sq_pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
} else if (pkt)
|
||||
return write_packet(mux, ost, pkt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void thread_set_name(OutputFile *of)
|
||||
{
|
||||
char name[16];
|
||||
snprintf(name, sizeof(name), "mux%d:%s", of->index, of->format->name);
|
||||
ff_thread_setname(name);
|
||||
}
|
||||
|
||||
static void *muxer_thread(void *arg)
|
||||
{
|
||||
Muxer *mux = arg;
|
||||
OutputFile *of = &mux->of;
|
||||
AVPacket *pkt = NULL;
|
||||
int ret = 0;
|
||||
|
||||
pkt = av_packet_alloc();
|
||||
if (!pkt) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
thread_set_name(of);
|
||||
|
||||
while (1) {
|
||||
OutputStream *ost;
|
||||
int stream_idx, stream_eof = 0;
|
||||
|
||||
ret = tq_receive(mux->tq, &stream_idx, pkt);
|
||||
if (stream_idx < 0) {
|
||||
av_log(mux, AV_LOG_VERBOSE, "All streams finished\n");
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
ost = of->streams[stream_idx];
|
||||
ret = sync_queue_process(mux, ost, ret < 0 ? NULL : pkt, &stream_eof);
|
||||
av_packet_unref(pkt);
|
||||
if (ret == AVERROR_EOF && stream_eof)
|
||||
tq_receive_finish(mux->tq, stream_idx);
|
||||
else if (ret < 0) {
|
||||
av_log(mux, AV_LOG_ERROR, "Error muxing a packet\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
finish:
|
||||
av_packet_free(&pkt);
|
||||
|
||||
for (unsigned int i = 0; i < mux->fc->nb_streams; i++)
|
||||
tq_receive_finish(mux->tq, i);
|
||||
|
||||
av_log(mux, AV_LOG_VERBOSE, "Terminating muxer thread\n");
|
||||
|
||||
return (void*)(intptr_t)ret;
|
||||
}
|
||||
|
||||
static int thread_submit_packet(Muxer *mux, OutputStream *ost, AVPacket *pkt)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!pkt || ost->finished & MUXER_FINISHED)
|
||||
goto finish;
|
||||
|
||||
ret = tq_send(mux->tq, ost->index, pkt);
|
||||
if (ret < 0)
|
||||
goto finish;
|
||||
|
||||
return 0;
|
||||
|
||||
finish:
|
||||
if (pkt)
|
||||
av_packet_unref(pkt);
|
||||
|
||||
ost->finished |= MUXER_FINISHED;
|
||||
tq_send_finish(mux->tq, ost->index);
|
||||
return ret == AVERROR_EOF ? 0 : ret;
|
||||
}
|
||||
|
||||
static int queue_packet(Muxer *mux, OutputStream *ost, AVPacket *pkt)
|
||||
{
|
||||
MuxStream *ms = ms_from_ost(ost);
|
||||
AVPacket *tmp_pkt = NULL;
|
||||
int ret;
|
||||
|
||||
if (!av_fifo_can_write(ms->muxing_queue)) {
|
||||
size_t cur_size = av_fifo_can_read(ms->muxing_queue);
|
||||
size_t pkt_size = pkt ? pkt->size : 0;
|
||||
unsigned int are_we_over_size =
|
||||
(ms->muxing_queue_data_size + pkt_size) > ms->muxing_queue_data_threshold;
|
||||
size_t limit = are_we_over_size ? ms->max_muxing_queue_size : SIZE_MAX;
|
||||
size_t new_size = FFMIN(2 * cur_size, limit);
|
||||
|
||||
if (new_size <= cur_size) {
|
||||
av_log(ost, AV_LOG_ERROR,
|
||||
"Too many packets buffered for output stream %d:%d.\n",
|
||||
ost->file_index, ost->st->index);
|
||||
return AVERROR(ENOSPC);
|
||||
}
|
||||
ret = av_fifo_grow2(ms->muxing_queue, new_size - cur_size);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (pkt) {
|
||||
ret = av_packet_make_refcounted(pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
tmp_pkt = av_packet_alloc();
|
||||
if (!tmp_pkt)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
av_packet_move_ref(tmp_pkt, pkt);
|
||||
ms->muxing_queue_data_size += tmp_pkt->size;
|
||||
}
|
||||
av_fifo_write(ms->muxing_queue, &tmp_pkt, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int submit_packet(Muxer *mux, AVPacket *pkt, OutputStream *ost)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (mux->tq) {
|
||||
return thread_submit_packet(mux, ost, pkt);
|
||||
} else {
|
||||
/* the muxer is not initialized yet, buffer the packet */
|
||||
ret = queue_packet(mux, ost, pkt);
|
||||
if (ret < 0) {
|
||||
if (pkt)
|
||||
av_packet_unref(pkt);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void of_output_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, int eof)
|
||||
{
|
||||
Muxer *mux = mux_from_of(of);
|
||||
MuxStream *ms = ms_from_ost(ost);
|
||||
const char *err_msg;
|
||||
int ret = 0;
|
||||
|
||||
if (!eof && pkt->dts != AV_NOPTS_VALUE)
|
||||
ost->last_mux_dts = av_rescale_q(pkt->dts, pkt->time_base, AV_TIME_BASE_Q);
|
||||
|
||||
/* apply the output bitstream filters */
|
||||
if (ms->bsf_ctx) {
|
||||
int bsf_eof = 0;
|
||||
|
||||
ret = av_bsf_send_packet(ms->bsf_ctx, eof ? NULL : pkt);
|
||||
if (ret < 0) {
|
||||
err_msg = "submitting a packet for bitstream filtering";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
while (!bsf_eof) {
|
||||
ret = av_bsf_receive_packet(ms->bsf_ctx, pkt);
|
||||
if (ret == AVERROR(EAGAIN))
|
||||
return;
|
||||
else if (ret == AVERROR_EOF)
|
||||
bsf_eof = 1;
|
||||
else if (ret < 0) {
|
||||
err_msg = "applying bitstream filters to a packet";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = submit_packet(mux, bsf_eof ? NULL : pkt, ost);
|
||||
if (ret < 0)
|
||||
goto mux_fail;
|
||||
}
|
||||
} else {
|
||||
ret = submit_packet(mux, eof ? NULL : pkt, ost);
|
||||
if (ret < 0)
|
||||
goto mux_fail;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
mux_fail:
|
||||
err_msg = "submitting a packet to the muxer";
|
||||
|
||||
fail:
|
||||
av_log(ost, AV_LOG_ERROR, "Error %s\n", err_msg);
|
||||
if (exit_on_error)
|
||||
exit_program(1);
|
||||
|
||||
}
|
||||
|
||||
static int thread_stop(Muxer *mux)
|
||||
{
|
||||
void *ret;
|
||||
|
||||
if (!mux || !mux->tq)
|
||||
return 0;
|
||||
|
||||
for (unsigned int i = 0; i < mux->fc->nb_streams; i++)
|
||||
tq_send_finish(mux->tq, i);
|
||||
|
||||
pthread_join(mux->thread, &ret);
|
||||
|
||||
tq_free(&mux->tq);
|
||||
|
||||
return (int)(intptr_t)ret;
|
||||
}
|
||||
|
||||
static void pkt_move(void *dst, void *src)
|
||||
{
|
||||
av_packet_move_ref(dst, src);
|
||||
}
|
||||
|
||||
static int thread_start(Muxer *mux)
|
||||
{
|
||||
AVFormatContext *fc = mux->fc;
|
||||
ObjPool *op;
|
||||
int ret;
|
||||
|
||||
op = objpool_alloc_packets();
|
||||
if (!op)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
mux->tq = tq_alloc(fc->nb_streams, mux->thread_queue_size, op, pkt_move);
|
||||
if (!mux->tq) {
|
||||
objpool_free(&op);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
ret = pthread_create(&mux->thread, NULL, muxer_thread, (void*)mux);
|
||||
if (ret) {
|
||||
tq_free(&mux->tq);
|
||||
return AVERROR(ret);
|
||||
}
|
||||
|
||||
/* flush the muxing queues */
|
||||
for (int i = 0; i < fc->nb_streams; i++) {
|
||||
OutputStream *ost = mux->of.streams[i];
|
||||
MuxStream *ms = ms_from_ost(ost);
|
||||
AVPacket *pkt;
|
||||
|
||||
/* try to improve muxing time_base (only possible if nothing has been written yet) */
|
||||
if (!av_fifo_can_read(ms->muxing_queue))
|
||||
ost->mux_timebase = ost->st->time_base;
|
||||
|
||||
while (av_fifo_read(ms->muxing_queue, &pkt, 1) >= 0) {
|
||||
ret = thread_submit_packet(mux, ost, pkt);
|
||||
if (pkt) {
|
||||
ms->muxing_queue_data_size -= pkt->size;
|
||||
av_packet_free(&pkt);
|
||||
}
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int print_sdp(void)
|
||||
{
|
||||
char sdp[16384];
|
||||
int i;
|
||||
int j, ret;
|
||||
AVIOContext *sdp_pb;
|
||||
AVFormatContext **avc;
|
||||
|
||||
for (i = 0; i < nb_output_files; i++) {
|
||||
if (!mux_from_of(output_files[i])->header_written)
|
||||
return 0;
|
||||
}
|
||||
|
||||
avc = av_malloc_array(nb_output_files, sizeof(*avc));
|
||||
if (!avc)
|
||||
return AVERROR(ENOMEM);
|
||||
for (i = 0, j = 0; i < nb_output_files; i++) {
|
||||
if (!strcmp(output_files[i]->format->name, "rtp")) {
|
||||
avc[j] = mux_from_of(output_files[i])->fc;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!j) {
|
||||
av_log(NULL, AV_LOG_ERROR, "No output streams in the SDP.\n");
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = av_sdp_create(avc, j, sdp, sizeof(sdp));
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
if (!sdp_filename) {
|
||||
av_log(NULL, AV_LOG_ERROR, "SDP:\n%s\n", sdp);
|
||||
fflush(stdout);
|
||||
} else {
|
||||
ret = avio_open2(&sdp_pb, sdp_filename, AVIO_FLAG_WRITE, &int_cb, NULL);
|
||||
if (ret < 0) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Failed to open sdp file '%s'\n", sdp_filename);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
avio_print(sdp_pb, sdp);
|
||||
avio_closep(&sdp_pb);
|
||||
av_freep(&sdp_filename);
|
||||
}
|
||||
|
||||
// SDP successfully written, allow muxer threads to start
|
||||
ret = 1;
|
||||
|
||||
fail:
|
||||
av_freep(&avc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mux_check_init(Muxer *mux)
|
||||
{
|
||||
OutputFile *of = &mux->of;
|
||||
AVFormatContext *fc = mux->fc;
|
||||
int ret, i;
|
||||
|
||||
for (i = 0; i < fc->nb_streams; i++) {
|
||||
OutputStream *ost = of->streams[i];
|
||||
if (!ost->initialized)
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = avformat_write_header(fc, &mux->opts);
|
||||
if (ret < 0) {
|
||||
av_log(mux, AV_LOG_ERROR, "Could not write header (incorrect codec "
|
||||
"parameters ?): %s\n", av_err2str(ret));
|
||||
return ret;
|
||||
}
|
||||
//assert_avoptions(of->opts);
|
||||
mux->header_written = 1;
|
||||
|
||||
av_dump_format(fc, of->index, fc->url, 1);
|
||||
nb_output_dumped++;
|
||||
|
||||
if (sdp_filename || want_sdp) {
|
||||
ret = print_sdp();
|
||||
if (ret < 0) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Error writing the SDP.\n");
|
||||
return ret;
|
||||
} else if (ret == 1) {
|
||||
/* SDP is written only after all the muxers are ready, so now we
|
||||
* start ALL the threads */
|
||||
for (i = 0; i < nb_output_files; i++) {
|
||||
ret = thread_start(mux_from_of(output_files[i]));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ret = thread_start(mux_from_of(of));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bsf_init(MuxStream *ms)
|
||||
{
|
||||
OutputStream *ost = &ms->ost;
|
||||
AVBSFContext *ctx = ms->bsf_ctx;
|
||||
int ret;
|
||||
|
||||
if (!ctx)
|
||||
return 0;
|
||||
|
||||
ret = avcodec_parameters_copy(ctx->par_in, ost->st->codecpar);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ctx->time_base_in = ost->st->time_base;
|
||||
|
||||
ret = av_bsf_init(ctx);
|
||||
if (ret < 0) {
|
||||
av_log(ms, AV_LOG_ERROR, "Error initializing bitstream filter: %s\n",
|
||||
ctx->filter->name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = avcodec_parameters_copy(ost->st->codecpar, ctx->par_out);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ost->st->time_base = ctx->time_base_out;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int of_stream_init(OutputFile *of, OutputStream *ost)
|
||||
{
|
||||
Muxer *mux = mux_from_of(of);
|
||||
MuxStream *ms = ms_from_ost(ost);
|
||||
int ret;
|
||||
|
||||
if (ost->sq_idx_mux >= 0)
|
||||
sq_set_tb(mux->sq_mux, ost->sq_idx_mux, ost->mux_timebase);
|
||||
|
||||
/* initialize bitstream filters for the output stream
|
||||
* needs to be done here, because the codec id for streamcopy is not
|
||||
* known until now */
|
||||
ret = bsf_init(ms);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ost->initialized = 1;
|
||||
|
||||
return mux_check_init(mux);
|
||||
}
|
||||
|
||||
int of_write_trailer(OutputFile *of)
|
||||
{
|
||||
Muxer *mux = mux_from_of(of);
|
||||
AVFormatContext *fc = mux->fc;
|
||||
int ret;
|
||||
|
||||
if (!mux->tq) {
|
||||
av_log(mux, AV_LOG_ERROR,
|
||||
"Nothing was written into output file, because "
|
||||
"at least one of its streams received no packets.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
ret = thread_stop(mux);
|
||||
if (ret < 0)
|
||||
main_ffmpeg_return_code = ret;
|
||||
|
||||
ret = av_write_trailer(fc);
|
||||
if (ret < 0) {
|
||||
av_log(mux, AV_LOG_ERROR, "Error writing trailer: %s\n", av_err2str(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
mux->last_filesize = filesize(fc->pb);
|
||||
|
||||
if (!(of->format->flags & AVFMT_NOFILE)) {
|
||||
ret = avio_closep(&fc->pb);
|
||||
if (ret < 0) {
|
||||
av_log(mux, AV_LOG_ERROR, "Error closing file: %s\n", av_err2str(ret));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ost_free(OutputStream **post)
|
||||
{
|
||||
OutputStream *ost = *post;
|
||||
MuxStream *ms;
|
||||
|
||||
if (!ost)
|
||||
return;
|
||||
ms = ms_from_ost(ost);
|
||||
|
||||
if (ost->logfile) {
|
||||
if (fclose(ost->logfile))
|
||||
av_log(ms, AV_LOG_ERROR,
|
||||
"Error closing logfile, loss of information possible: %s\n",
|
||||
av_err2str(AVERROR(errno)));
|
||||
ost->logfile = NULL;
|
||||
}
|
||||
|
||||
if (ms->muxing_queue) {
|
||||
AVPacket *pkt;
|
||||
while (av_fifo_read(ms->muxing_queue, &pkt, 1) >= 0)
|
||||
av_packet_free(&pkt);
|
||||
av_fifo_freep2(&ms->muxing_queue);
|
||||
}
|
||||
|
||||
av_bsf_free(&ms->bsf_ctx);
|
||||
|
||||
av_frame_free(&ost->filtered_frame);
|
||||
av_frame_free(&ost->sq_frame);
|
||||
av_frame_free(&ost->last_frame);
|
||||
av_packet_free(&ost->pkt);
|
||||
av_dict_free(&ost->encoder_opts);
|
||||
|
||||
av_freep(&ost->kf.pts);
|
||||
av_expr_free(ost->kf.pexpr);
|
||||
|
||||
av_freep(&ost->avfilter);
|
||||
av_freep(&ost->logfile_prefix);
|
||||
av_freep(&ost->apad);
|
||||
|
||||
#if FFMPEG_OPT_MAP_CHANNEL
|
||||
av_freep(&ost->audio_channels_map);
|
||||
ost->audio_channels_mapped = 0;
|
||||
#endif
|
||||
|
||||
av_dict_free(&ost->sws_dict);
|
||||
av_dict_free(&ost->swr_opts);
|
||||
|
||||
if (ost->enc_ctx)
|
||||
av_freep(&ost->enc_ctx->stats_in);
|
||||
avcodec_free_context(&ost->enc_ctx);
|
||||
|
||||
for (int i = 0; i < ost->enc_stats_pre.nb_components; i++)
|
||||
av_freep(&ost->enc_stats_pre.components[i].str);
|
||||
av_freep(&ost->enc_stats_pre.components);
|
||||
|
||||
for (int i = 0; i < ost->enc_stats_post.nb_components; i++)
|
||||
av_freep(&ost->enc_stats_post.components[i].str);
|
||||
av_freep(&ost->enc_stats_post.components);
|
||||
|
||||
for (int i = 0; i < ms->stats.nb_components; i++)
|
||||
av_freep(&ms->stats.components[i].str);
|
||||
av_freep(&ms->stats.components);
|
||||
|
||||
av_freep(post);
|
||||
}
|
||||
|
||||
static void fc_close(AVFormatContext **pfc)
|
||||
{
|
||||
AVFormatContext *fc = *pfc;
|
||||
|
||||
if (!fc)
|
||||
return;
|
||||
|
||||
if (!(fc->oformat->flags & AVFMT_NOFILE))
|
||||
avio_closep(&fc->pb);
|
||||
avformat_free_context(fc);
|
||||
|
||||
*pfc = NULL;
|
||||
}
|
||||
|
||||
void of_close(OutputFile **pof)
|
||||
{
|
||||
OutputFile *of = *pof;
|
||||
Muxer *mux;
|
||||
|
||||
if (!of)
|
||||
return;
|
||||
mux = mux_from_of(of);
|
||||
|
||||
thread_stop(mux);
|
||||
|
||||
sq_free(&of->sq_encode);
|
||||
sq_free(&mux->sq_mux);
|
||||
|
||||
for (int i = 0; i < of->nb_streams; i++)
|
||||
ost_free(&of->streams[i]);
|
||||
av_freep(&of->streams);
|
||||
|
||||
av_dict_free(&mux->opts);
|
||||
|
||||
av_packet_free(&mux->sq_pkt);
|
||||
|
||||
fc_close(&mux->fc);
|
||||
|
||||
av_freep(pof);
|
||||
}
|
||||
|
||||
int64_t of_filesize(OutputFile *of)
|
||||
{
|
||||
Muxer *mux = mux_from_of(of);
|
||||
return atomic_load(&mux->last_filesize);
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Muxer internal APIs - should not be included outside of ffmpeg_mux*
|
||||
* Copyright (c) 2023 ARTHENICA LTD
|
||||
*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is the modified version of ffmpeg_mux.h file living in ffmpeg source code under the fftools folder. We
|
||||
* manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied
|
||||
* by us to develop ffmpeg-kit library.
|
||||
*
|
||||
* ffmpeg-kit changes by ARTHENICA LTD
|
||||
*
|
||||
* 07.2023
|
||||
* --------------------------------------------------------
|
||||
* - FFmpeg 6.0 changes migrated
|
||||
* - fftools header names updated
|
||||
* - want_sdp made thread-local
|
||||
* - EncStatsFile declaration migrated from ffmpeg_mux_init.c
|
||||
* - WARN_MULTIPLE_OPT_USAGE, MATCH_PER_STREAM_OPT, MATCH_PER_TYPE_OPT, SPECIFIER_OPT_FMT declarations migrated from
|
||||
* ffmpeg.h
|
||||
* - ms_from_ost migrated to ffmpeg_mux.c
|
||||
*/
|
||||
|
||||
#ifndef FFTOOLS_FFMPEG_MUX_H
|
||||
#define FFTOOLS_FFMPEG_MUX_H
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "fftools_thread_queue.h"
|
||||
|
||||
#include "libavformat/avformat.h"
|
||||
|
||||
#include "libavcodec/packet.h"
|
||||
|
||||
#include "libavutil/dict.h"
|
||||
#include "libavutil/fifo.h"
|
||||
#include "libavutil/thread.h"
|
||||
|
||||
#define SPECIFIER_OPT_FMT_str "%s"
|
||||
#define SPECIFIER_OPT_FMT_i "%i"
|
||||
#define SPECIFIER_OPT_FMT_i64 "%"PRId64
|
||||
#define SPECIFIER_OPT_FMT_ui64 "%"PRIu64
|
||||
#define SPECIFIER_OPT_FMT_f "%f"
|
||||
#define SPECIFIER_OPT_FMT_dbl "%lf"
|
||||
|
||||
#define WARN_MULTIPLE_OPT_USAGE(name, type, so, st)\
|
||||
{\
|
||||
char namestr[128] = "";\
|
||||
const char *spec = so->specifier && so->specifier[0] ? so->specifier : "";\
|
||||
for (int _i = 0; opt_name_##name[_i]; _i++)\
|
||||
av_strlcatf(namestr, sizeof(namestr), "-%s%s", opt_name_##name[_i], opt_name_##name[_i+1] ? (opt_name_##name[_i+2] ? ", " : " or ") : "");\
|
||||
av_log(NULL, AV_LOG_WARNING, "Multiple %s options specified for stream %d, only the last option '-%s%s%s "SPECIFIER_OPT_FMT_##type"' will be used.\n",\
|
||||
namestr, st->index, opt_name_##name[0], spec[0] ? ":" : "", spec, so->u.type);\
|
||||
}
|
||||
|
||||
#define MATCH_PER_STREAM_OPT(name, type, outvar, fmtctx, st)\
|
||||
{\
|
||||
int _ret, _matches = 0;\
|
||||
SpecifierOpt *so;\
|
||||
for (int _i = 0; _i < o->nb_ ## name; _i++) {\
|
||||
char *spec = o->name[_i].specifier;\
|
||||
if ((_ret = check_stream_specifier(fmtctx, st, spec)) > 0) {\
|
||||
outvar = o->name[_i].u.type;\
|
||||
so = &o->name[_i];\
|
||||
_matches++;\
|
||||
} else if (_ret < 0)\
|
||||
exit_program(1);\
|
||||
}\
|
||||
if (_matches > 1)\
|
||||
WARN_MULTIPLE_OPT_USAGE(name, type, so, st);\
|
||||
}
|
||||
|
||||
#define MATCH_PER_TYPE_OPT(name, type, outvar, fmtctx, mediatype)\
|
||||
{\
|
||||
int i;\
|
||||
for (i = 0; i < o->nb_ ## name; i++) {\
|
||||
char *spec = o->name[i].specifier;\
|
||||
if (!strcmp(spec, mediatype))\
|
||||
outvar = o->name[i].u.type;\
|
||||
}\
|
||||
}
|
||||
|
||||
typedef struct MuxStream {
|
||||
OutputStream ost;
|
||||
|
||||
// name used for logging
|
||||
char log_name[32];
|
||||
|
||||
/* the packets are buffered here until the muxer is ready to be initialized */
|
||||
AVFifo *muxing_queue;
|
||||
|
||||
AVBSFContext *bsf_ctx;
|
||||
|
||||
EncStats stats;
|
||||
|
||||
int64_t max_frames;
|
||||
|
||||
/*
|
||||
* The size of the AVPackets' buffers in queue.
|
||||
* Updated when a packet is either pushed or pulled from the queue.
|
||||
*/
|
||||
size_t muxing_queue_data_size;
|
||||
|
||||
int max_muxing_queue_size;
|
||||
|
||||
/* Threshold after which max_muxing_queue_size will be in effect */
|
||||
size_t muxing_queue_data_threshold;
|
||||
|
||||
/* dts of the last packet sent to the muxer, in the stream timebase
|
||||
* used for making up missing dts values */
|
||||
int64_t last_mux_dts;
|
||||
} MuxStream;
|
||||
|
||||
typedef struct Muxer {
|
||||
OutputFile of;
|
||||
|
||||
// name used for logging
|
||||
char log_name[32];
|
||||
|
||||
AVFormatContext *fc;
|
||||
|
||||
pthread_t thread;
|
||||
ThreadQueue *tq;
|
||||
|
||||
AVDictionary *opts;
|
||||
|
||||
int thread_queue_size;
|
||||
|
||||
/* filesize limit expressed in bytes */
|
||||
int64_t limit_filesize;
|
||||
atomic_int_least64_t last_filesize;
|
||||
int header_written;
|
||||
|
||||
SyncQueue *sq_mux;
|
||||
AVPacket *sq_pkt;
|
||||
} Muxer;
|
||||
|
||||
typedef struct EncStatsFile {
|
||||
char *path;
|
||||
AVIOContext *io;
|
||||
} EncStatsFile;
|
||||
|
||||
/* whether we want to print an SDP, set in of_open() */
|
||||
extern __thread int want_sdp;
|
||||
|
||||
int mux_check_init(Muxer *mux);
|
||||
|
||||
#endif /* FFTOOLS_FFMPEG_MUX_H */
|
||||
+2414
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is the modified version of fopen_utf8.h file living in ffmpeg source code under the fftools folder. We
|
||||
* manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied
|
||||
* by us to develop the ffmpeg-kit library.
|
||||
*
|
||||
* ffmpeg-kit changes by Taner Sener
|
||||
*/
|
||||
|
||||
#ifndef FFTOOLS_FOPEN_UTF8_H
|
||||
#define FFTOOLS_FOPEN_UTF8_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* The fopen_utf8 function here is essentially equivalent to avpriv_fopen_utf8,
|
||||
* except that it doesn't set O_CLOEXEC, and that it isn't exported
|
||||
* from a different library. (On Windows, each DLL might use a different
|
||||
* CRT, and FILE* handles can't be shared across them.) */
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "libavutil/wchar_filename.h"
|
||||
|
||||
static inline FILE *fopen_utf8(const char *path_utf8, const char *mode)
|
||||
{
|
||||
wchar_t *path_w, *mode_w;
|
||||
FILE *f;
|
||||
|
||||
/* convert UTF-8 to wide chars */
|
||||
if (get_extended_win32_path(path_utf8, &path_w)) /* This sets errno on error. */
|
||||
return NULL;
|
||||
if (!path_w)
|
||||
goto fallback;
|
||||
|
||||
if (utf8towchar(mode, &mode_w))
|
||||
return NULL;
|
||||
if (!mode_w) {
|
||||
/* If failing to interpret the mode string as utf8, it is an invalid
|
||||
* parameter. */
|
||||
av_freep(&path_w);
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
f = _wfopen(path_w, mode_w);
|
||||
av_freep(&path_w);
|
||||
av_freep(&mode_w);
|
||||
|
||||
return f;
|
||||
fallback:
|
||||
/* path may be in CP_ACP */
|
||||
return fopen(path_utf8, mode);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline FILE *fopen_utf8(const char *path, const char *mode)
|
||||
{
|
||||
return fopen(path, mode);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* FFTOOLS_FOPEN_UTF8_H */
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
* Copyright (c) 2023 ARTHENICA LTD
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is the modified version of objpool.c file living in ffmpeg source code under the fftools folder. We
|
||||
* manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied
|
||||
* by us to develop ffmpeg-kit library.
|
||||
*
|
||||
* ffmpeg-kit changes by ARTHENICA LTD
|
||||
*
|
||||
* 07.2023
|
||||
* --------------------------------------------------------
|
||||
* - FFmpeg 6.0 changes migrated
|
||||
* - fftools header names updated
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "libavcodec/packet.h"
|
||||
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/error.h"
|
||||
#include "libavutil/frame.h"
|
||||
#include "libavutil/mem.h"
|
||||
|
||||
#include "fftools_objpool.h"
|
||||
|
||||
struct ObjPool {
|
||||
void *pool[32];
|
||||
unsigned int pool_count;
|
||||
|
||||
ObjPoolCBAlloc alloc;
|
||||
ObjPoolCBReset reset;
|
||||
ObjPoolCBFree free;
|
||||
};
|
||||
|
||||
ObjPool *objpool_alloc(ObjPoolCBAlloc cb_alloc, ObjPoolCBReset cb_reset,
|
||||
ObjPoolCBFree cb_free)
|
||||
{
|
||||
ObjPool *op = av_mallocz(sizeof(*op));
|
||||
|
||||
if (!op)
|
||||
return NULL;
|
||||
|
||||
op->alloc = cb_alloc;
|
||||
op->reset = cb_reset;
|
||||
op->free = cb_free;
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
void objpool_free(ObjPool **pop)
|
||||
{
|
||||
ObjPool *op = *pop;
|
||||
|
||||
if (!op)
|
||||
return;
|
||||
|
||||
for (unsigned int i = 0; i < op->pool_count; i++)
|
||||
op->free(&op->pool[i]);
|
||||
|
||||
av_freep(pop);
|
||||
}
|
||||
|
||||
int objpool_get(ObjPool *op, void **obj)
|
||||
{
|
||||
if (op->pool_count) {
|
||||
*obj = op->pool[--op->pool_count];
|
||||
op->pool[op->pool_count] = NULL;
|
||||
} else
|
||||
*obj = op->alloc();
|
||||
|
||||
return *obj ? 0 : AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
void objpool_release(ObjPool *op, void **obj)
|
||||
{
|
||||
if (!*obj)
|
||||
return;
|
||||
|
||||
op->reset(*obj);
|
||||
|
||||
if (op->pool_count < FF_ARRAY_ELEMS(op->pool))
|
||||
op->pool[op->pool_count++] = *obj;
|
||||
else
|
||||
op->free(obj);
|
||||
|
||||
*obj = NULL;
|
||||
}
|
||||
|
||||
static void *alloc_packet(void)
|
||||
{
|
||||
return av_packet_alloc();
|
||||
}
|
||||
static void *alloc_frame(void)
|
||||
{
|
||||
return av_frame_alloc();
|
||||
}
|
||||
|
||||
static void reset_packet(void *obj)
|
||||
{
|
||||
av_packet_unref(obj);
|
||||
}
|
||||
static void reset_frame(void *obj)
|
||||
{
|
||||
av_frame_unref(obj);
|
||||
}
|
||||
|
||||
static void free_packet(void **obj)
|
||||
{
|
||||
AVPacket *pkt = *obj;
|
||||
av_packet_free(&pkt);
|
||||
*obj = NULL;
|
||||
}
|
||||
static void free_frame(void **obj)
|
||||
{
|
||||
AVFrame *frame = *obj;
|
||||
av_frame_free(&frame);
|
||||
*obj = NULL;
|
||||
}
|
||||
|
||||
ObjPool *objpool_alloc_packets(void)
|
||||
{
|
||||
return objpool_alloc(alloc_packet, reset_packet, free_packet);
|
||||
}
|
||||
ObjPool *objpool_alloc_frames(void)
|
||||
{
|
||||
return objpool_alloc(alloc_frame, reset_frame, free_frame);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
* Copyright (c) 2023 ARTHENICA LTD
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is the modified version of objpool.h file living in ffmpeg source code under the fftools folder. We
|
||||
* manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied
|
||||
* by us to develop ffmpeg-kit library.
|
||||
*
|
||||
* ffmpeg-kit changes by ARTHENICA LTD
|
||||
*
|
||||
* 07.2023
|
||||
* --------------------------------------------------------
|
||||
* - FFmpeg 6.0 changes migrated
|
||||
*/
|
||||
|
||||
#ifndef FFTOOLS_OBJPOOL_H
|
||||
#define FFTOOLS_OBJPOOL_H
|
||||
|
||||
typedef struct ObjPool ObjPool;
|
||||
|
||||
typedef void* (*ObjPoolCBAlloc)(void);
|
||||
typedef void (*ObjPoolCBReset)(void *);
|
||||
typedef void (*ObjPoolCBFree)(void **);
|
||||
|
||||
void objpool_free(ObjPool **op);
|
||||
ObjPool *objpool_alloc(ObjPoolCBAlloc cb_alloc, ObjPoolCBReset cb_reset,
|
||||
ObjPoolCBFree cb_free);
|
||||
ObjPool *objpool_alloc_packets(void);
|
||||
ObjPool *objpool_alloc_frames(void);
|
||||
|
||||
int objpool_get(ObjPool *op, void **obj);
|
||||
void objpool_release(ObjPool *op, void **obj);
|
||||
|
||||
#endif // FFTOOLS_OBJPOOL_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Option handlers shared between the tools.
|
||||
* Copyright (c) 2022 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is the modified version of opt_common.h file living in ffmpeg source code under the fftools folder. We
|
||||
* manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied
|
||||
* by us to develop the ffmpeg-kit library.
|
||||
*
|
||||
* ffmpeg-kit changes by Taner Sener
|
||||
*
|
||||
* 09.2022
|
||||
* --------------------------------------------------------
|
||||
* - CMDUTILS_COMMON_OPTIONS and CMDUTILS_COMMON_OPTIONS_AVDEVICE defines dropped
|
||||
* - fftools_ prefix added to fftools headers
|
||||
*/
|
||||
|
||||
#ifndef FFTOOLS_OPT_COMMON_H
|
||||
#define FFTOOLS_OPT_COMMON_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "fftools_cmdutils.h"
|
||||
|
||||
#if CONFIG_AVDEVICE
|
||||
/**
|
||||
* Print a listing containing autodetected sinks of the output device.
|
||||
* Device name with options may be passed as an argument to limit results.
|
||||
*/
|
||||
int show_sinks(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing autodetected sources of the input device.
|
||||
* Device name with options may be passed as an argument to limit results.
|
||||
*/
|
||||
int show_sources(void *optctx, const char *opt, const char *arg);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Print the license of the program to stdout. The license depends on
|
||||
* the license of the libraries compiled into the program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_license(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Generic -h handler common to all fftools.
|
||||
*/
|
||||
int show_help(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print the version of the program to stdout. The version message
|
||||
* depends on the current versions of the repository and of the libav*
|
||||
* libraries.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_version(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print the build configuration of the program to stdout. The contents
|
||||
* depend on the definition of FFMPEG_CONFIGURATION.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_buildconf(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the formats supported by the
|
||||
* program (including devices).
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_formats(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the muxers supported by the
|
||||
* program (including devices).
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_muxers(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the demuxer supported by the
|
||||
* program (including devices).
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_demuxers(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the devices supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_devices(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the codecs supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_codecs(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the decoders supported by the
|
||||
* program.
|
||||
*/
|
||||
int show_decoders(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the encoders supported by the
|
||||
* program.
|
||||
*/
|
||||
int show_encoders(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the bit stream filters supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_bsfs(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the protocols supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_protocols(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the filters supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_filters(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the pixel formats supported by the
|
||||
* program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_pix_fmts(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the standard channel layouts supported by
|
||||
* the program.
|
||||
* This option processing function does not utilize the arguments.
|
||||
*/
|
||||
int show_layouts(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the sample formats supported by the
|
||||
* program.
|
||||
*/
|
||||
int show_sample_fmts(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all supported stream dispositions.
|
||||
*/
|
||||
int show_dispositions(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Print a listing containing all the color names and values recognized
|
||||
* by the program.
|
||||
*/
|
||||
int show_colors(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Set the libav* libraries log level.
|
||||
*/
|
||||
int opt_loglevel(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
int opt_report(void *optctx, const char *opt, const char *arg);
|
||||
int init_report(const char *env, FILE **file);
|
||||
|
||||
int opt_max_alloc(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Override the cpuflags.
|
||||
*/
|
||||
int opt_cpuflags(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
/**
|
||||
* Override the cpucount.
|
||||
*/
|
||||
int opt_cpucount(void *optctx, const char *opt, const char *arg);
|
||||
|
||||
#endif /* FFTOOLS_OPT_COMMON_H */
|
||||
@@ -0,0 +1,462 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
* Copyright (c) 2023 ARTHENICA LTD
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is the modified version of sync_queue.c file living in ffmpeg source code under the fftools folder. We
|
||||
* manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied
|
||||
* by us to develop ffmpeg-kit library.
|
||||
*
|
||||
* ffmpeg-kit changes by ARTHENICA LTD
|
||||
*
|
||||
* 07.2023
|
||||
* --------------------------------------------------------
|
||||
* - FFmpeg 6.0 changes migrated
|
||||
* - fftools header names updated
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/error.h"
|
||||
#include "libavutil/fifo.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavutil/mem.h"
|
||||
|
||||
#include "fftools_objpool.h"
|
||||
#include "fftools_sync_queue.h"
|
||||
|
||||
typedef struct SyncQueueStream {
|
||||
AVFifo *fifo;
|
||||
AVRational tb;
|
||||
|
||||
/* stream head: largest timestamp seen */
|
||||
int64_t head_ts;
|
||||
int limiting;
|
||||
/* no more frames will be sent for this stream */
|
||||
int finished;
|
||||
|
||||
uint64_t frames_sent;
|
||||
uint64_t frames_max;
|
||||
} SyncQueueStream;
|
||||
|
||||
struct SyncQueue {
|
||||
enum SyncQueueType type;
|
||||
|
||||
/* no more frames will be sent for any stream */
|
||||
int finished;
|
||||
/* sync head: the stream with the _smallest_ head timestamp
|
||||
* this stream determines which frames can be output */
|
||||
int head_stream;
|
||||
/* the finished stream with the smallest finish timestamp or -1 */
|
||||
int head_finished_stream;
|
||||
|
||||
// maximum buffering duration in microseconds
|
||||
int64_t buf_size_us;
|
||||
|
||||
SyncQueueStream *streams;
|
||||
unsigned int nb_streams;
|
||||
|
||||
// pool of preallocated frames to avoid constant allocations
|
||||
ObjPool *pool;
|
||||
};
|
||||
|
||||
static void frame_move(const SyncQueue *sq, SyncQueueFrame dst,
|
||||
SyncQueueFrame src)
|
||||
{
|
||||
if (sq->type == SYNC_QUEUE_PACKETS)
|
||||
av_packet_move_ref(dst.p, src.p);
|
||||
else
|
||||
av_frame_move_ref(dst.f, src.f);
|
||||
}
|
||||
|
||||
static int64_t frame_ts(const SyncQueue *sq, SyncQueueFrame frame)
|
||||
{
|
||||
return (sq->type == SYNC_QUEUE_PACKETS) ?
|
||||
frame.p->pts + frame.p->duration :
|
||||
frame.f->pts + frame.f->duration;
|
||||
}
|
||||
|
||||
static int frame_null(const SyncQueue *sq, SyncQueueFrame frame)
|
||||
{
|
||||
return (sq->type == SYNC_QUEUE_PACKETS) ? (frame.p == NULL) : (frame.f == NULL);
|
||||
}
|
||||
|
||||
static void finish_stream(SyncQueue *sq, unsigned int stream_idx)
|
||||
{
|
||||
SyncQueueStream *st = &sq->streams[stream_idx];
|
||||
|
||||
st->finished = 1;
|
||||
|
||||
if (st->limiting && st->head_ts != AV_NOPTS_VALUE) {
|
||||
/* check if this stream is the new finished head */
|
||||
if (sq->head_finished_stream < 0 ||
|
||||
av_compare_ts(st->head_ts, st->tb,
|
||||
sq->streams[sq->head_finished_stream].head_ts,
|
||||
sq->streams[sq->head_finished_stream].tb) < 0) {
|
||||
sq->head_finished_stream = stream_idx;
|
||||
}
|
||||
|
||||
/* mark as finished all streams that should no longer receive new frames,
|
||||
* due to them being ahead of some finished stream */
|
||||
st = &sq->streams[sq->head_finished_stream];
|
||||
for (unsigned int i = 0; i < sq->nb_streams; i++) {
|
||||
SyncQueueStream *st1 = &sq->streams[i];
|
||||
if (st != st1 && st1->head_ts != AV_NOPTS_VALUE &&
|
||||
av_compare_ts(st->head_ts, st->tb, st1->head_ts, st1->tb) <= 0)
|
||||
st1->finished = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* mark the whole queue as finished if all streams are finished */
|
||||
for (unsigned int i = 0; i < sq->nb_streams; i++) {
|
||||
if (!sq->streams[i].finished)
|
||||
return;
|
||||
}
|
||||
sq->finished = 1;
|
||||
}
|
||||
|
||||
static void queue_head_update(SyncQueue *sq)
|
||||
{
|
||||
if (sq->head_stream < 0) {
|
||||
/* wait for one timestamp in each stream before determining
|
||||
* the queue head */
|
||||
for (unsigned int i = 0; i < sq->nb_streams; i++) {
|
||||
SyncQueueStream *st = &sq->streams[i];
|
||||
if (st->limiting && st->head_ts == AV_NOPTS_VALUE)
|
||||
return;
|
||||
}
|
||||
|
||||
// placeholder value, correct one will be found below
|
||||
sq->head_stream = 0;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < sq->nb_streams; i++) {
|
||||
SyncQueueStream *st_head = &sq->streams[sq->head_stream];
|
||||
SyncQueueStream *st_other = &sq->streams[i];
|
||||
if (st_other->limiting && st_other->head_ts != AV_NOPTS_VALUE &&
|
||||
av_compare_ts(st_other->head_ts, st_other->tb,
|
||||
st_head->head_ts, st_head->tb) < 0)
|
||||
sq->head_stream = i;
|
||||
}
|
||||
}
|
||||
|
||||
/* update this stream's head timestamp */
|
||||
static void stream_update_ts(SyncQueue *sq, unsigned int stream_idx, int64_t ts)
|
||||
{
|
||||
SyncQueueStream *st = &sq->streams[stream_idx];
|
||||
|
||||
if (ts == AV_NOPTS_VALUE ||
|
||||
(st->head_ts != AV_NOPTS_VALUE && st->head_ts >= ts))
|
||||
return;
|
||||
|
||||
st->head_ts = ts;
|
||||
|
||||
/* if this stream is now ahead of some finished stream, then
|
||||
* this stream is also finished */
|
||||
if (sq->head_finished_stream >= 0 &&
|
||||
av_compare_ts(sq->streams[sq->head_finished_stream].head_ts,
|
||||
sq->streams[sq->head_finished_stream].tb,
|
||||
ts, st->tb) <= 0)
|
||||
finish_stream(sq, stream_idx);
|
||||
|
||||
/* update the overall head timestamp if it could have changed */
|
||||
if (st->limiting &&
|
||||
(sq->head_stream < 0 || sq->head_stream == stream_idx))
|
||||
queue_head_update(sq);
|
||||
}
|
||||
|
||||
/* If the queue for the given stream (or all streams when stream_idx=-1)
|
||||
* is overflowing, trigger a fake heartbeat on lagging streams.
|
||||
*
|
||||
* @return 1 if heartbeat triggered, 0 otherwise
|
||||
*/
|
||||
static int overflow_heartbeat(SyncQueue *sq, int stream_idx)
|
||||
{
|
||||
SyncQueueStream *st;
|
||||
SyncQueueFrame frame;
|
||||
int64_t tail_ts = AV_NOPTS_VALUE;
|
||||
|
||||
/* if no stream specified, pick the one that is most ahead */
|
||||
if (stream_idx < 0) {
|
||||
int64_t ts = AV_NOPTS_VALUE;
|
||||
|
||||
for (int i = 0; i < sq->nb_streams; i++) {
|
||||
st = &sq->streams[i];
|
||||
if (st->head_ts != AV_NOPTS_VALUE &&
|
||||
(ts == AV_NOPTS_VALUE ||
|
||||
av_compare_ts(ts, sq->streams[stream_idx].tb,
|
||||
st->head_ts, st->tb) < 0)) {
|
||||
ts = st->head_ts;
|
||||
stream_idx = i;
|
||||
}
|
||||
}
|
||||
/* no stream has a timestamp yet -> nothing to do */
|
||||
if (stream_idx < 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
st = &sq->streams[stream_idx];
|
||||
|
||||
/* get the chosen stream's tail timestamp */
|
||||
for (size_t i = 0; tail_ts == AV_NOPTS_VALUE &&
|
||||
av_fifo_peek(st->fifo, &frame, 1, i) >= 0; i++)
|
||||
tail_ts = frame_ts(sq, frame);
|
||||
|
||||
/* overflow triggers when the tail is over specified duration behind the head */
|
||||
if (tail_ts == AV_NOPTS_VALUE || tail_ts >= st->head_ts ||
|
||||
av_rescale_q(st->head_ts - tail_ts, st->tb, AV_TIME_BASE_Q) < sq->buf_size_us)
|
||||
return 0;
|
||||
|
||||
/* signal a fake timestamp for all streams that prevent tail_ts from being output */
|
||||
tail_ts++;
|
||||
for (unsigned int i = 0; i < sq->nb_streams; i++) {
|
||||
SyncQueueStream *st1 = &sq->streams[i];
|
||||
int64_t ts;
|
||||
|
||||
if (st == st1 || st1->finished ||
|
||||
(st1->head_ts != AV_NOPTS_VALUE &&
|
||||
av_compare_ts(tail_ts, st->tb, st1->head_ts, st1->tb) <= 0))
|
||||
continue;
|
||||
|
||||
ts = av_rescale_q(tail_ts, st->tb, st1->tb);
|
||||
if (st1->head_ts != AV_NOPTS_VALUE)
|
||||
ts = FFMAX(st1->head_ts + 1, ts);
|
||||
|
||||
stream_update_ts(sq, i, ts);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int sq_send(SyncQueue *sq, unsigned int stream_idx, SyncQueueFrame frame)
|
||||
{
|
||||
SyncQueueStream *st;
|
||||
SyncQueueFrame dst;
|
||||
int64_t ts;
|
||||
int ret;
|
||||
|
||||
av_assert0(stream_idx < sq->nb_streams);
|
||||
st = &sq->streams[stream_idx];
|
||||
|
||||
av_assert0(st->tb.num > 0 && st->tb.den > 0);
|
||||
|
||||
if (frame_null(sq, frame)) {
|
||||
finish_stream(sq, stream_idx);
|
||||
return 0;
|
||||
}
|
||||
if (st->finished)
|
||||
return AVERROR_EOF;
|
||||
|
||||
ret = objpool_get(sq->pool, (void**)&dst);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
frame_move(sq, dst, frame);
|
||||
|
||||
ts = frame_ts(sq, dst);
|
||||
|
||||
ret = av_fifo_write(st->fifo, &dst, 1);
|
||||
if (ret < 0) {
|
||||
frame_move(sq, frame, dst);
|
||||
objpool_release(sq->pool, (void**)&dst);
|
||||
return ret;
|
||||
}
|
||||
|
||||
stream_update_ts(sq, stream_idx, ts);
|
||||
|
||||
st->frames_sent++;
|
||||
if (st->frames_sent >= st->frames_max)
|
||||
finish_stream(sq, stream_idx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int receive_for_stream(SyncQueue *sq, unsigned int stream_idx,
|
||||
SyncQueueFrame frame)
|
||||
{
|
||||
SyncQueueStream *st_head = sq->head_stream >= 0 ?
|
||||
&sq->streams[sq->head_stream] : NULL;
|
||||
SyncQueueStream *st;
|
||||
|
||||
av_assert0(stream_idx < sq->nb_streams);
|
||||
st = &sq->streams[stream_idx];
|
||||
|
||||
if (av_fifo_can_read(st->fifo)) {
|
||||
SyncQueueFrame peek;
|
||||
int64_t ts;
|
||||
int cmp = 1;
|
||||
|
||||
av_fifo_peek(st->fifo, &peek, 1, 0);
|
||||
ts = frame_ts(sq, peek);
|
||||
|
||||
/* check if this stream's tail timestamp does not overtake
|
||||
* the overall queue head */
|
||||
if (ts != AV_NOPTS_VALUE && st_head)
|
||||
cmp = av_compare_ts(ts, st->tb, st_head->head_ts, st_head->tb);
|
||||
|
||||
/* We can release frames that do not end after the queue head.
|
||||
* Frames with no timestamps are just passed through with no conditions.
|
||||
*/
|
||||
if (cmp <= 0 || ts == AV_NOPTS_VALUE) {
|
||||
frame_move(sq, frame, peek);
|
||||
objpool_release(sq->pool, (void**)&peek);
|
||||
av_fifo_drain2(st->fifo, 1);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return (sq->finished || (st->finished && !av_fifo_can_read(st->fifo))) ?
|
||||
AVERROR_EOF : AVERROR(EAGAIN);
|
||||
}
|
||||
|
||||
static int receive_internal(SyncQueue *sq, int stream_idx, SyncQueueFrame frame)
|
||||
{
|
||||
int nb_eof = 0;
|
||||
int ret;
|
||||
|
||||
/* read a frame for a specific stream */
|
||||
if (stream_idx >= 0) {
|
||||
ret = receive_for_stream(sq, stream_idx, frame);
|
||||
return (ret < 0) ? ret : stream_idx;
|
||||
}
|
||||
|
||||
/* read a frame for any stream with available output */
|
||||
for (unsigned int i = 0; i < sq->nb_streams; i++) {
|
||||
ret = receive_for_stream(sq, i, frame);
|
||||
if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) {
|
||||
nb_eof += (ret == AVERROR_EOF);
|
||||
continue;
|
||||
}
|
||||
return (ret < 0) ? ret : i;
|
||||
}
|
||||
|
||||
return (nb_eof == sq->nb_streams) ? AVERROR_EOF : AVERROR(EAGAIN);
|
||||
}
|
||||
|
||||
int sq_receive(SyncQueue *sq, int stream_idx, SyncQueueFrame frame)
|
||||
{
|
||||
int ret = receive_internal(sq, stream_idx, frame);
|
||||
|
||||
/* try again if the queue overflowed and triggered a fake heartbeat
|
||||
* for lagging streams */
|
||||
if (ret == AVERROR(EAGAIN) && overflow_heartbeat(sq, stream_idx))
|
||||
ret = receive_internal(sq, stream_idx, frame);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int sq_add_stream(SyncQueue *sq, int limiting)
|
||||
{
|
||||
SyncQueueStream *tmp, *st;
|
||||
|
||||
tmp = av_realloc_array(sq->streams, sq->nb_streams + 1, sizeof(*sq->streams));
|
||||
if (!tmp)
|
||||
return AVERROR(ENOMEM);
|
||||
sq->streams = tmp;
|
||||
|
||||
st = &sq->streams[sq->nb_streams];
|
||||
memset(st, 0, sizeof(*st));
|
||||
|
||||
st->fifo = av_fifo_alloc2(1, sizeof(SyncQueueFrame), AV_FIFO_FLAG_AUTO_GROW);
|
||||
if (!st->fifo)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
/* we set a valid default, so that a pathological stream that never
|
||||
* receives even a real timebase (and no frames) won't stall all other
|
||||
* streams forever; cf. overflow_heartbeat() */
|
||||
st->tb = (AVRational){ 1, 1 };
|
||||
st->head_ts = AV_NOPTS_VALUE;
|
||||
st->frames_max = UINT64_MAX;
|
||||
st->limiting = limiting;
|
||||
|
||||
return sq->nb_streams++;
|
||||
}
|
||||
|
||||
void sq_set_tb(SyncQueue *sq, unsigned int stream_idx, AVRational tb)
|
||||
{
|
||||
SyncQueueStream *st;
|
||||
|
||||
av_assert0(stream_idx < sq->nb_streams);
|
||||
st = &sq->streams[stream_idx];
|
||||
|
||||
av_assert0(!av_fifo_can_read(st->fifo));
|
||||
|
||||
if (st->head_ts != AV_NOPTS_VALUE)
|
||||
st->head_ts = av_rescale_q(st->head_ts, st->tb, tb);
|
||||
|
||||
st->tb = tb;
|
||||
}
|
||||
|
||||
void sq_limit_frames(SyncQueue *sq, unsigned int stream_idx, uint64_t frames)
|
||||
{
|
||||
SyncQueueStream *st;
|
||||
|
||||
av_assert0(stream_idx < sq->nb_streams);
|
||||
st = &sq->streams[stream_idx];
|
||||
|
||||
st->frames_max = frames;
|
||||
if (st->frames_sent >= st->frames_max)
|
||||
finish_stream(sq, stream_idx);
|
||||
}
|
||||
|
||||
SyncQueue *sq_alloc(enum SyncQueueType type, int64_t buf_size_us)
|
||||
{
|
||||
SyncQueue *sq = av_mallocz(sizeof(*sq));
|
||||
|
||||
if (!sq)
|
||||
return NULL;
|
||||
|
||||
sq->type = type;
|
||||
sq->buf_size_us = buf_size_us;
|
||||
|
||||
sq->head_stream = -1;
|
||||
sq->head_finished_stream = -1;
|
||||
|
||||
sq->pool = (type == SYNC_QUEUE_PACKETS) ? objpool_alloc_packets() :
|
||||
objpool_alloc_frames();
|
||||
if (!sq->pool) {
|
||||
av_freep(&sq);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return sq;
|
||||
}
|
||||
|
||||
void sq_free(SyncQueue **psq)
|
||||
{
|
||||
SyncQueue *sq = *psq;
|
||||
|
||||
if (!sq)
|
||||
return;
|
||||
|
||||
for (unsigned int i = 0; i < sq->nb_streams; i++) {
|
||||
SyncQueueFrame frame;
|
||||
while (av_fifo_read(sq->streams[i].fifo, &frame, 1) >= 0)
|
||||
objpool_release(sq->pool, (void**)&frame);
|
||||
|
||||
av_fifo_freep2(&sq->streams[i].fifo);
|
||||
}
|
||||
|
||||
av_freep(&sq->streams);
|
||||
|
||||
objpool_free(&sq->pool);
|
||||
|
||||
av_freep(psq);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
* Copyright (c) 2023 ARTHENICA LTD
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is the modified version of sync_queue.h file living in ffmpeg source code under the fftools folder. We
|
||||
* manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied
|
||||
* by us to develop ffmpeg-kit library.
|
||||
*
|
||||
* ffmpeg-kit changes by ARTHENICA LTD
|
||||
*
|
||||
* 07.2023
|
||||
* --------------------------------------------------------
|
||||
* - FFmpeg 6.0 changes migrated
|
||||
*/
|
||||
|
||||
#ifndef FFTOOLS_SYNC_QUEUE_H
|
||||
#define FFTOOLS_SYNC_QUEUE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "libavcodec/packet.h"
|
||||
|
||||
#include "libavutil/frame.h"
|
||||
|
||||
enum SyncQueueType {
|
||||
SYNC_QUEUE_PACKETS,
|
||||
SYNC_QUEUE_FRAMES,
|
||||
};
|
||||
|
||||
typedef union SyncQueueFrame {
|
||||
AVFrame *f;
|
||||
AVPacket *p;
|
||||
} SyncQueueFrame;
|
||||
|
||||
#define SQFRAME(frame) ((SyncQueueFrame){ .f = (frame) })
|
||||
#define SQPKT(pkt) ((SyncQueueFrame){ .p = (pkt) })
|
||||
|
||||
typedef struct SyncQueue SyncQueue;
|
||||
|
||||
/**
|
||||
* Allocate a sync queue of the given type.
|
||||
*
|
||||
* @param buf_size_us maximum duration that will be buffered in microseconds
|
||||
*/
|
||||
SyncQueue *sq_alloc(enum SyncQueueType type, int64_t buf_size_us);
|
||||
void sq_free(SyncQueue **sq);
|
||||
|
||||
/**
|
||||
* Add a new stream to the sync queue.
|
||||
*
|
||||
* @param limiting whether the stream is limiting, i.e. no other stream can be
|
||||
* longer than this one
|
||||
* @return
|
||||
* - a non-negative stream index on success
|
||||
* - a negative error code on error
|
||||
*/
|
||||
int sq_add_stream(SyncQueue *sq, int limiting);
|
||||
|
||||
/**
|
||||
* Set the timebase for the stream with index stream_idx. Should be called
|
||||
* before sending any frames for this stream.
|
||||
*/
|
||||
void sq_set_tb(SyncQueue *sq, unsigned int stream_idx, AVRational tb);
|
||||
|
||||
/**
|
||||
* Limit the number of output frames for stream with index stream_idx
|
||||
* to max_frames.
|
||||
*/
|
||||
void sq_limit_frames(SyncQueue *sq, unsigned int stream_idx,
|
||||
uint64_t max_frames);
|
||||
|
||||
/**
|
||||
* Submit a frame for the stream with index stream_idx.
|
||||
*
|
||||
* On success, the sync queue takes ownership of the frame and will reset the
|
||||
* contents of the supplied frame. On failure, the frame remains owned by the
|
||||
* caller.
|
||||
*
|
||||
* Sending a frame with NULL contents marks the stream as finished.
|
||||
*
|
||||
* @return
|
||||
* - 0 on success
|
||||
* - AVERROR_EOF when no more frames should be submitted for this stream
|
||||
* - another a negative error code on failure
|
||||
*/
|
||||
int sq_send(SyncQueue *sq, unsigned int stream_idx, SyncQueueFrame frame);
|
||||
|
||||
/**
|
||||
* Read a frame from the queue.
|
||||
*
|
||||
* @param stream_idx index of the stream to read a frame for. May be -1, then
|
||||
* try to read a frame from any stream that is ready for
|
||||
* output.
|
||||
* @param frame output frame will be written here on success. The frame is owned
|
||||
* by the caller.
|
||||
*
|
||||
* @return
|
||||
* - a non-negative index of the stream to which the returned frame belongs
|
||||
* - AVERROR(EAGAIN) when more frames need to be submitted to the queue
|
||||
* - AVERROR_EOF when no more frames will be available for this stream (for any
|
||||
* stream if stream_idx is -1)
|
||||
* - another negative error code on failure
|
||||
*/
|
||||
int sq_receive(SyncQueue *sq, int stream_idx, SyncQueueFrame frame);
|
||||
|
||||
#endif // FFTOOLS_SYNC_QUEUE_H
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
* Copyright (c) 2023 ARTHENICA LTD
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is the modified version of thread_queue.c file living in ffmpeg source code under the fftools folder. We
|
||||
* manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied
|
||||
* by us to develop ffmpeg-kit library.
|
||||
*
|
||||
* ffmpeg-kit changes by ARTHENICA LTD
|
||||
*
|
||||
* 07.2023
|
||||
* --------------------------------------------------------
|
||||
* - FFmpeg 6.0 changes migrated
|
||||
* - fftools header names updated
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/error.h"
|
||||
#include "libavutil/fifo.h"
|
||||
#include "libavutil/intreadwrite.h"
|
||||
#include "libavutil/mem.h"
|
||||
#include "libavutil/thread.h"
|
||||
|
||||
#include "fftools_objpool.h"
|
||||
#include "fftools_thread_queue.h"
|
||||
|
||||
enum {
|
||||
FINISHED_SEND = (1 << 0),
|
||||
FINISHED_RECV = (1 << 1),
|
||||
};
|
||||
|
||||
typedef struct FifoElem {
|
||||
void *obj;
|
||||
unsigned int stream_idx;
|
||||
} FifoElem;
|
||||
|
||||
struct ThreadQueue {
|
||||
int *finished;
|
||||
unsigned int nb_streams;
|
||||
|
||||
AVFifo *fifo;
|
||||
|
||||
ObjPool *obj_pool;
|
||||
void (*obj_move)(void *dst, void *src);
|
||||
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond;
|
||||
};
|
||||
|
||||
void tq_free(ThreadQueue **ptq)
|
||||
{
|
||||
ThreadQueue *tq = *ptq;
|
||||
|
||||
if (!tq)
|
||||
return;
|
||||
|
||||
if (tq->fifo) {
|
||||
FifoElem elem;
|
||||
while (av_fifo_read(tq->fifo, &elem, 1) >= 0)
|
||||
objpool_release(tq->obj_pool, &elem.obj);
|
||||
}
|
||||
av_fifo_freep2(&tq->fifo);
|
||||
|
||||
objpool_free(&tq->obj_pool);
|
||||
|
||||
av_freep(&tq->finished);
|
||||
|
||||
pthread_cond_destroy(&tq->cond);
|
||||
pthread_mutex_destroy(&tq->lock);
|
||||
|
||||
av_freep(ptq);
|
||||
}
|
||||
|
||||
ThreadQueue *tq_alloc(unsigned int nb_streams, size_t queue_size,
|
||||
ObjPool *obj_pool, void (*obj_move)(void *dst, void *src))
|
||||
{
|
||||
ThreadQueue *tq;
|
||||
int ret;
|
||||
|
||||
tq = av_mallocz(sizeof(*tq));
|
||||
if (!tq)
|
||||
return NULL;
|
||||
|
||||
ret = pthread_cond_init(&tq->cond, NULL);
|
||||
if (ret) {
|
||||
av_freep(&tq);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = pthread_mutex_init(&tq->lock, NULL);
|
||||
if (ret) {
|
||||
pthread_cond_destroy(&tq->cond);
|
||||
av_freep(&tq);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tq->finished = av_calloc(nb_streams, sizeof(*tq->finished));
|
||||
if (!tq->finished)
|
||||
goto fail;
|
||||
tq->nb_streams = nb_streams;
|
||||
|
||||
tq->fifo = av_fifo_alloc2(queue_size, sizeof(FifoElem), 0);
|
||||
if (!tq->fifo)
|
||||
goto fail;
|
||||
|
||||
tq->obj_pool = obj_pool;
|
||||
tq->obj_move = obj_move;
|
||||
|
||||
return tq;
|
||||
fail:
|
||||
tq_free(&tq);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int tq_send(ThreadQueue *tq, unsigned int stream_idx, void *data)
|
||||
{
|
||||
int *finished;
|
||||
int ret;
|
||||
|
||||
av_assert0(stream_idx < tq->nb_streams);
|
||||
finished = &tq->finished[stream_idx];
|
||||
|
||||
pthread_mutex_lock(&tq->lock);
|
||||
|
||||
if (*finished & FINISHED_SEND) {
|
||||
ret = AVERROR(EINVAL);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
while (!(*finished & FINISHED_RECV) && !av_fifo_can_write(tq->fifo))
|
||||
pthread_cond_wait(&tq->cond, &tq->lock);
|
||||
|
||||
if (*finished & FINISHED_RECV) {
|
||||
ret = AVERROR_EOF;
|
||||
*finished |= FINISHED_SEND;
|
||||
} else {
|
||||
FifoElem elem = { .stream_idx = stream_idx };
|
||||
|
||||
ret = objpool_get(tq->obj_pool, &elem.obj);
|
||||
if (ret < 0)
|
||||
goto finish;
|
||||
|
||||
tq->obj_move(elem.obj, data);
|
||||
|
||||
ret = av_fifo_write(tq->fifo, &elem, 1);
|
||||
av_assert0(ret >= 0);
|
||||
pthread_cond_broadcast(&tq->cond);
|
||||
}
|
||||
|
||||
finish:
|
||||
pthread_mutex_unlock(&tq->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int receive_locked(ThreadQueue *tq, int *stream_idx,
|
||||
void *data)
|
||||
{
|
||||
FifoElem elem;
|
||||
unsigned int nb_finished = 0;
|
||||
|
||||
if (av_fifo_read(tq->fifo, &elem, 1) >= 0) {
|
||||
tq->obj_move(data, elem.obj);
|
||||
objpool_release(tq->obj_pool, &elem.obj);
|
||||
*stream_idx = elem.stream_idx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < tq->nb_streams; i++) {
|
||||
if (!(tq->finished[i] & FINISHED_SEND))
|
||||
continue;
|
||||
|
||||
/* return EOF to the consumer at most once for each stream */
|
||||
if (!(tq->finished[i] & FINISHED_RECV)) {
|
||||
tq->finished[i] |= FINISHED_RECV;
|
||||
*stream_idx = i;
|
||||
return AVERROR_EOF;
|
||||
}
|
||||
|
||||
nb_finished++;
|
||||
}
|
||||
|
||||
return nb_finished == tq->nb_streams ? AVERROR_EOF : AVERROR(EAGAIN);
|
||||
}
|
||||
|
||||
int tq_receive(ThreadQueue *tq, int *stream_idx, void *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*stream_idx = -1;
|
||||
|
||||
pthread_mutex_lock(&tq->lock);
|
||||
|
||||
while (1) {
|
||||
ret = receive_locked(tq, stream_idx, data);
|
||||
if (ret == AVERROR(EAGAIN)) {
|
||||
pthread_cond_wait(&tq->cond, &tq->lock);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
pthread_cond_broadcast(&tq->cond);
|
||||
|
||||
pthread_mutex_unlock(&tq->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void tq_send_finish(ThreadQueue *tq, unsigned int stream_idx)
|
||||
{
|
||||
av_assert0(stream_idx < tq->nb_streams);
|
||||
|
||||
pthread_mutex_lock(&tq->lock);
|
||||
|
||||
/* mark the stream as send-finished;
|
||||
* next time the consumer thread tries to read this stream it will get
|
||||
* an EOF and recv-finished flag will be set */
|
||||
tq->finished[stream_idx] |= FINISHED_SEND;
|
||||
pthread_cond_broadcast(&tq->cond);
|
||||
|
||||
pthread_mutex_unlock(&tq->lock);
|
||||
}
|
||||
|
||||
void tq_receive_finish(ThreadQueue *tq, unsigned int stream_idx)
|
||||
{
|
||||
av_assert0(stream_idx < tq->nb_streams);
|
||||
|
||||
pthread_mutex_lock(&tq->lock);
|
||||
|
||||
/* mark the stream as recv-finished;
|
||||
* next time the producer thread tries to send for this stream, it will
|
||||
* get an EOF and send-finished flag will be set */
|
||||
tq->finished[stream_idx] |= FINISHED_RECV;
|
||||
pthread_cond_broadcast(&tq->cond);
|
||||
|
||||
pthread_mutex_unlock(&tq->lock);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
* Copyright (c) 2023 ARTHENICA LTD
|
||||
*
|
||||
* FFmpeg 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 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is the modified version of thread_queue.h file living in ffmpeg source code under the fftools folder. We
|
||||
* manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied
|
||||
* by us to develop ffmpeg-kit library.
|
||||
*
|
||||
* ffmpeg-kit changes by ARTHENICA LTD
|
||||
*
|
||||
* 07.2023
|
||||
* --------------------------------------------------------
|
||||
* - FFmpeg 6.0 changes migrated
|
||||
*/
|
||||
|
||||
#ifndef FFTOOLS_THREAD_QUEUE_H
|
||||
#define FFTOOLS_THREAD_QUEUE_H
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "fftools_objpool.h"
|
||||
|
||||
typedef struct ThreadQueue ThreadQueue;
|
||||
|
||||
/**
|
||||
* Allocate a queue for sending data between threads.
|
||||
*
|
||||
* @param nb_streams number of streams for which a distinct EOF state is
|
||||
* maintained
|
||||
* @param queue_size number of items that can be stored in the queue without
|
||||
* blocking
|
||||
* @param obj_pool object pool that will be used to allocate items stored in the
|
||||
* queue; the pool becomes owned by the queue
|
||||
* @param callback that moves the contents between two data pointers
|
||||
*/
|
||||
ThreadQueue *tq_alloc(unsigned int nb_streams, size_t queue_size,
|
||||
ObjPool *obj_pool, void (*obj_move)(void *dst, void *src));
|
||||
void tq_free(ThreadQueue **tq);
|
||||
|
||||
/**
|
||||
* Send an item for the given stream to the queue.
|
||||
*
|
||||
* @param data the item to send, its contents will be moved using the callback
|
||||
* provided to tq_alloc(); on failure the item will be left
|
||||
* untouched
|
||||
* @return
|
||||
* - 0 the item was successfully sent
|
||||
* - AVERROR(ENOMEM) could not allocate an item for writing to the FIFO
|
||||
* - AVERROR(EINVAL) the sending side has previously been marked as finished
|
||||
* - AVERROR_EOF the receiving side has marked the given stream as finished
|
||||
*/
|
||||
int tq_send(ThreadQueue *tq, unsigned int stream_idx, void *data);
|
||||
/**
|
||||
* Mark the given stream finished from the sending side.
|
||||
*/
|
||||
void tq_send_finish(ThreadQueue *tq, unsigned int stream_idx);
|
||||
|
||||
/**
|
||||
* Read the next item from the queue.
|
||||
*
|
||||
* @param stream_idx the index of the stream that was processed or -1 will be
|
||||
* written here
|
||||
* @param data the data item will be written here on success using the
|
||||
* callback provided to tq_alloc()
|
||||
* @return
|
||||
* - 0 a data item was successfully read; *stream_idx contains a non-negative
|
||||
* stream index
|
||||
* - AVERROR_EOF When *stream_idx is non-negative, this signals that the sending
|
||||
* side has marked the given stream as finished. This will happen at most once
|
||||
* for each stream. When *stream_idx is -1, all streams are done.
|
||||
*/
|
||||
int tq_receive(ThreadQueue *tq, int *stream_idx, void *data);
|
||||
/**
|
||||
* Mark the given stream finished from the receiving side.
|
||||
*/
|
||||
void tq_receive_finish(ThreadQueue *tq, unsigned int stream_idx);
|
||||
|
||||
#endif // FFTOOLS_THREAD_QUEUE_H
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
/**
|
||||
* <p>Enumeration for Android ABIs.
|
||||
*/
|
||||
public enum Abi {
|
||||
|
||||
/**
|
||||
* Represents armeabi-v7a ABI with NEON support
|
||||
*/
|
||||
ABI_ARMV7A_NEON("armeabi-v7a-neon"),
|
||||
|
||||
/**
|
||||
* Represents armeabi-v7a ABI
|
||||
*/
|
||||
ABI_ARMV7A("armeabi-v7a"),
|
||||
|
||||
/**
|
||||
* Represents armeabi ABI
|
||||
*/
|
||||
ABI_ARM("armeabi"),
|
||||
|
||||
/**
|
||||
* Represents x86 ABI
|
||||
*/
|
||||
ABI_X86("x86"),
|
||||
|
||||
/**
|
||||
* Represents x86_64 ABI
|
||||
*/
|
||||
ABI_X86_64("x86_64"),
|
||||
|
||||
/**
|
||||
* Represents arm64-v8a ABI
|
||||
*/
|
||||
ABI_ARM64_V8A("arm64-v8a"),
|
||||
|
||||
/**
|
||||
* Represents not supported ABIs
|
||||
*/
|
||||
ABI_UNKNOWN("unknown");
|
||||
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* <p>Returns the enumeration defined for the given ABI name.
|
||||
*
|
||||
* @param abiName ABI name
|
||||
* @return enumeration defined for the ABI name
|
||||
*/
|
||||
public static Abi from(final String abiName) {
|
||||
if (abiName == null) {
|
||||
return ABI_UNKNOWN;
|
||||
} else if (abiName.equals(ABI_ARM.getName())) {
|
||||
return ABI_ARM;
|
||||
} else if (abiName.equals(ABI_ARMV7A.getName())) {
|
||||
return ABI_ARMV7A;
|
||||
} else if (abiName.equals(ABI_ARMV7A_NEON.getName())) {
|
||||
return ABI_ARMV7A_NEON;
|
||||
} else if (abiName.equals(ABI_ARM64_V8A.getName())) {
|
||||
return ABI_ARM64_V8A;
|
||||
} else if (abiName.equals(ABI_X86.getName())) {
|
||||
return ABI_X86;
|
||||
} else if (abiName.equals(ABI_X86_64.getName())) {
|
||||
return ABI_X86_64;
|
||||
} else {
|
||||
return ABI_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ABI name.
|
||||
*
|
||||
* @return ABI name as defined in Android NDK documentation
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new enum.
|
||||
*
|
||||
* @param abiName ABI name
|
||||
*/
|
||||
Abi(final String abiName) {
|
||||
this.name = abiName;
|
||||
}
|
||||
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
/**
|
||||
* <p>Detects the running ABI name natively using Google <code>cpu-features</code> library.
|
||||
*/
|
||||
public class AbiDetect {
|
||||
|
||||
static {
|
||||
armV7aNeonLoaded = false;
|
||||
|
||||
NativeLoader.loadFFmpegKitAbiDetect();
|
||||
|
||||
/* ALL LIBRARIES LOADED AT STARTUP */
|
||||
FFmpegKit.class.getName();
|
||||
FFmpegKitConfig.class.getName();
|
||||
FFprobeKit.class.getName();
|
||||
}
|
||||
|
||||
static final String ARM_V7A = "arm-v7a";
|
||||
|
||||
static final String ARM_V7A_NEON = "arm-v7a-neon";
|
||||
|
||||
private static boolean armV7aNeonLoaded;
|
||||
|
||||
/**
|
||||
* Default constructor hidden.
|
||||
*/
|
||||
private AbiDetect() {
|
||||
}
|
||||
|
||||
static void setArmV7aNeonLoaded() {
|
||||
armV7aNeonLoaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the ABI name loaded.
|
||||
*
|
||||
* @return ABI name loaded
|
||||
*/
|
||||
public static String getAbi() {
|
||||
if (armV7aNeonLoaded) {
|
||||
return ARM_V7A_NEON;
|
||||
} else {
|
||||
return getNativeAbi();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the ABI name of the cpu running.
|
||||
*
|
||||
* @return ABI name of the cpu running
|
||||
*/
|
||||
public static String getCpuAbi() {
|
||||
return getNativeCpuAbi();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the ABI name loaded natively.
|
||||
*
|
||||
* @return ABI name loaded
|
||||
*/
|
||||
native static String getNativeAbi();
|
||||
|
||||
/**
|
||||
* <p>Returns the ABI name of the cpu running natively.
|
||||
*
|
||||
* @return ABI name of the cpu running
|
||||
*/
|
||||
native static String getNativeCpuAbi();
|
||||
|
||||
/**
|
||||
* <p>Returns whether FFmpegKit release is a long term release or not natively.
|
||||
*
|
||||
* @return yes or no
|
||||
*/
|
||||
native static boolean isNativeLTSBuild();
|
||||
|
||||
/**
|
||||
* <p>Returns the build configuration for <code>FFmpeg</code> natively.
|
||||
*
|
||||
* @return build configuration string
|
||||
*/
|
||||
native static String getNativeBuildConf();
|
||||
|
||||
}
|
||||
+356
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* Copyright (c) 2021 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import com.arthenica.smartexception.java.Exceptions;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Abstract session implementation which includes common features shared by <code>FFmpeg</code>,
|
||||
* <code>FFprobe</code> and <code>MediaInformation</code> sessions.
|
||||
*/
|
||||
public abstract class AbstractSession implements Session {
|
||||
|
||||
/**
|
||||
* Generates unique ids for sessions.
|
||||
*/
|
||||
protected static final AtomicLong sessionIdGenerator = new AtomicLong(1);
|
||||
|
||||
/**
|
||||
* Defines how long default "getAll" methods wait, in milliseconds.
|
||||
*/
|
||||
public static final int DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT = 5000;
|
||||
|
||||
/**
|
||||
* Session identifier.
|
||||
*/
|
||||
protected final long sessionId;
|
||||
|
||||
/**
|
||||
* Session specific log callback.
|
||||
*/
|
||||
protected final LogCallback logCallback;
|
||||
|
||||
/**
|
||||
* Date and time the session was created.
|
||||
*/
|
||||
protected final Date createTime;
|
||||
|
||||
/**
|
||||
* Date and time the session was started.
|
||||
*/
|
||||
protected Date startTime;
|
||||
|
||||
/**
|
||||
* Date and time the session has ended.
|
||||
*/
|
||||
protected Date endTime;
|
||||
|
||||
/**
|
||||
* Command arguments as an array.
|
||||
*/
|
||||
protected final String[] arguments;
|
||||
|
||||
/**
|
||||
* Log entries received for this session.
|
||||
*/
|
||||
protected final List<Log> logs;
|
||||
|
||||
/**
|
||||
* Log entry lock.
|
||||
*/
|
||||
protected final Object logsLock;
|
||||
|
||||
/**
|
||||
* Future created for sessions executed asynchronously.
|
||||
*/
|
||||
protected Future<?> future;
|
||||
|
||||
/**
|
||||
* State of the session.
|
||||
*/
|
||||
protected SessionState state;
|
||||
|
||||
/**
|
||||
* Return code for the completed sessions.
|
||||
*/
|
||||
protected ReturnCode returnCode;
|
||||
|
||||
/**
|
||||
* Stack trace of the error received while trying to execute this session.
|
||||
*/
|
||||
protected String failStackTrace;
|
||||
|
||||
/**
|
||||
* Session specific log redirection strategy.
|
||||
*/
|
||||
protected final LogRedirectionStrategy logRedirectionStrategy;
|
||||
|
||||
/**
|
||||
* Creates a new abstract session.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @param logCallback session specific log callback
|
||||
* @param logRedirectionStrategy session specific log redirection strategy
|
||||
*/
|
||||
protected AbstractSession(final String[] arguments,
|
||||
final LogCallback logCallback,
|
||||
final LogRedirectionStrategy logRedirectionStrategy) {
|
||||
this.sessionId = sessionIdGenerator.getAndIncrement();
|
||||
this.logCallback = logCallback;
|
||||
this.createTime = new Date();
|
||||
this.startTime = null;
|
||||
this.endTime = null;
|
||||
this.arguments = arguments;
|
||||
this.logs = new LinkedList<>();
|
||||
this.logsLock = new Object();
|
||||
this.future = null;
|
||||
this.state = SessionState.CREATED;
|
||||
this.returnCode = null;
|
||||
this.failStackTrace = null;
|
||||
this.logRedirectionStrategy = logRedirectionStrategy;
|
||||
|
||||
FFmpegKitConfig.addSession(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogCallback getLogCallback() {
|
||||
return logCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getEndTime() {
|
||||
return endTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
final Date startTime = this.startTime;
|
||||
final Date endTime = this.endTime;
|
||||
if (startTime != null && endTime != null) {
|
||||
return (endTime.getTime() - startTime.getTime());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommand() {
|
||||
return FFmpegKitConfig.argumentsToString(arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Log> getAllLogs(final int waitTimeout) {
|
||||
waitForAsynchronousMessagesInTransmit(waitTimeout);
|
||||
|
||||
if (thereAreAsynchronousMessagesInTransmit()) {
|
||||
android.util.Log.i(FFmpegKitConfig.TAG, String.format("getAllLogs was called to return all logs but there are still logs being transmitted for session id %d.", sessionId));
|
||||
}
|
||||
|
||||
return getLogs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all log entries generated for this session. If there are asynchronous
|
||||
* messages that are not delivered yet, this method waits for them until
|
||||
* {@link #DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT} expires.
|
||||
*
|
||||
* @return list of log entries generated for this session
|
||||
*/
|
||||
@Override
|
||||
public List<Log> getAllLogs() {
|
||||
return getAllLogs(DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Log> getLogs() {
|
||||
synchronized (logsLock) {
|
||||
return new LinkedList<>(logs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAllLogsAsString(final int waitTimeout) {
|
||||
waitForAsynchronousMessagesInTransmit(waitTimeout);
|
||||
|
||||
if (thereAreAsynchronousMessagesInTransmit()) {
|
||||
android.util.Log.i(FFmpegKitConfig.TAG, String.format("getAllLogsAsString was called to return all logs but there are still logs being transmitted for session id %d.", sessionId));
|
||||
}
|
||||
|
||||
return getLogsAsString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all log entries generated for this session as a concatenated string. If there are
|
||||
* asynchronous messages that are not delivered yet, this method waits for them until
|
||||
* {@link #DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT} expires.
|
||||
*
|
||||
* @return all log entries generated for this session as a concatenated string
|
||||
*/
|
||||
@Override
|
||||
public String getAllLogsAsString() {
|
||||
return getAllLogsAsString(DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogsAsString() {
|
||||
final StringBuilder concatenatedString = new StringBuilder();
|
||||
|
||||
synchronized (logsLock) {
|
||||
for (Log log : logs) {
|
||||
concatenatedString.append(log.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return concatenatedString.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOutput() {
|
||||
return getAllLogsAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReturnCode getReturnCode() {
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFailStackTrace() {
|
||||
return failStackTrace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogRedirectionStrategy getLogRedirectionStrategy() {
|
||||
return logRedirectionStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean thereAreAsynchronousMessagesInTransmit() {
|
||||
return (FFmpegKitConfig.messagesInTransmit(sessionId) != 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLog(final Log log) {
|
||||
synchronized (logsLock) {
|
||||
this.logs.add(log);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<?> getFuture() {
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (state == SessionState.RUNNING) {
|
||||
FFmpegKit.cancel(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for all asynchronous messages to be transmitted until the given timeout.
|
||||
*
|
||||
* @param timeout wait timeout in milliseconds
|
||||
*/
|
||||
protected void waitForAsynchronousMessagesInTransmit(final int timeout) {
|
||||
final long start = System.currentTimeMillis();
|
||||
|
||||
while (thereAreAsynchronousMessagesInTransmit() && (System.currentTimeMillis() < (start + timeout))) {
|
||||
synchronized (this) {
|
||||
try {
|
||||
wait(100);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the future created for this session.
|
||||
*
|
||||
* @param future future that runs this session asynchronously
|
||||
*/
|
||||
void setFuture(final Future<?> future) {
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts running the session.
|
||||
*/
|
||||
void startRunning() {
|
||||
this.state = SessionState.RUNNING;
|
||||
this.startTime = new Date();
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes running the session with the provided return code.
|
||||
*
|
||||
* @param returnCode return code of the execution
|
||||
*/
|
||||
void complete(final ReturnCode returnCode) {
|
||||
this.returnCode = returnCode;
|
||||
this.state = SessionState.COMPLETED;
|
||||
this.endTime = new Date();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends running the session with a failure.
|
||||
*
|
||||
* @param exception execution received
|
||||
*/
|
||||
void fail(final Exception exception) {
|
||||
this.failStackTrace = Exceptions.getStackTraceString(exception);
|
||||
this.state = SessionState.FAILED;
|
||||
this.endTime = new Date();
|
||||
}
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import com.arthenica.smartexception.java.Exceptions;
|
||||
|
||||
/**
|
||||
* <p>Executes an FFmpeg session asynchronously.
|
||||
*/
|
||||
public class AsyncFFmpegExecuteTask implements Runnable {
|
||||
private final FFmpegSession ffmpegSession;
|
||||
private final FFmpegSessionCompleteCallback completeCallback;
|
||||
|
||||
public AsyncFFmpegExecuteTask(final FFmpegSession ffmpegSession) {
|
||||
this.ffmpegSession = ffmpegSession;
|
||||
this.completeCallback = ffmpegSession.getCompleteCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
FFmpegKitConfig.ffmpegExecute(ffmpegSession);
|
||||
|
||||
if (completeCallback != null) {
|
||||
try {
|
||||
// NOTIFY SESSION CALLBACK DEFINED
|
||||
completeCallback.apply(ffmpegSession);
|
||||
} catch (final Exception e) {
|
||||
android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside session complete callback.%s", Exceptions.getStackTraceString(e)));
|
||||
}
|
||||
}
|
||||
|
||||
final FFmpegSessionCompleteCallback globalFFmpegSessionCompleteCallback = FFmpegKitConfig.getFFmpegSessionCompleteCallback();
|
||||
if (globalFFmpegSessionCompleteCallback != null) {
|
||||
try {
|
||||
// NOTIFY GLOBAL CALLBACK DEFINED
|
||||
globalFFmpegSessionCompleteCallback.apply(ffmpegSession);
|
||||
} catch (final Exception e) {
|
||||
android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside global complete callback.%s", Exceptions.getStackTraceString(e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import com.arthenica.smartexception.java.Exceptions;
|
||||
|
||||
/**
|
||||
* <p>Executes an FFprobe session asynchronously.
|
||||
*/
|
||||
public class AsyncFFprobeExecuteTask implements Runnable {
|
||||
private final FFprobeSession ffprobeSession;
|
||||
private final FFprobeSessionCompleteCallback completeCallback;
|
||||
|
||||
public AsyncFFprobeExecuteTask(final FFprobeSession ffprobeSession) {
|
||||
this.ffprobeSession = ffprobeSession;
|
||||
this.completeCallback = ffprobeSession.getCompleteCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
FFmpegKitConfig.ffprobeExecute(ffprobeSession);
|
||||
|
||||
if (completeCallback != null) {
|
||||
try {
|
||||
// NOTIFY SESSION CALLBACK DEFINED
|
||||
completeCallback.apply(ffprobeSession);
|
||||
} catch (final Exception e) {
|
||||
android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside session complete callback.%s", Exceptions.getStackTraceString(e)));
|
||||
}
|
||||
}
|
||||
|
||||
final FFprobeSessionCompleteCallback globalFFprobeSessionCompleteCallback = FFmpegKitConfig.getFFprobeSessionCompleteCallback();
|
||||
if (globalFFprobeSessionCompleteCallback != null) {
|
||||
try {
|
||||
// NOTIFY GLOBAL CALLBACK DEFINED
|
||||
globalFFprobeSessionCompleteCallback.apply(ffprobeSession);
|
||||
} catch (final Exception e) {
|
||||
android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside global complete callback.%s", Exceptions.getStackTraceString(e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import com.arthenica.smartexception.java.Exceptions;
|
||||
|
||||
/**
|
||||
* <p>Executes a MediaInformation session asynchronously.
|
||||
*/
|
||||
public class AsyncGetMediaInformationTask implements Runnable {
|
||||
private final MediaInformationSession mediaInformationSession;
|
||||
private final MediaInformationSessionCompleteCallback completeCallback;
|
||||
private final Integer waitTimeout;
|
||||
|
||||
public AsyncGetMediaInformationTask(final MediaInformationSession mediaInformationSession) {
|
||||
this(mediaInformationSession, AbstractSession.DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT);
|
||||
}
|
||||
|
||||
public AsyncGetMediaInformationTask(final MediaInformationSession mediaInformationSession, final Integer waitTimeout) {
|
||||
this.mediaInformationSession = mediaInformationSession;
|
||||
this.completeCallback = mediaInformationSession.getCompleteCallback();
|
||||
this.waitTimeout = waitTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
FFmpegKitConfig.getMediaInformationExecute(mediaInformationSession, waitTimeout);
|
||||
|
||||
if (completeCallback != null) {
|
||||
try {
|
||||
// NOTIFY SESSION CALLBACK DEFINED
|
||||
completeCallback.apply(mediaInformationSession);
|
||||
} catch (final Exception e) {
|
||||
android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside session complete callback.%s", Exceptions.getStackTraceString(e)));
|
||||
}
|
||||
}
|
||||
|
||||
final MediaInformationSessionCompleteCallback globalMediaInformationSessionCompleteCallback = FFmpegKitConfig.getMediaInformationSessionCompleteCallback();
|
||||
if (globalMediaInformationSessionCompleteCallback != null) {
|
||||
try {
|
||||
// NOTIFY GLOBAL CALLBACK DEFINEDs
|
||||
globalMediaInformationSessionCompleteCallback.apply(mediaInformationSession);
|
||||
} catch (final Exception e) {
|
||||
android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside global complete callback.%s", Exceptions.getStackTraceString(e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2021 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.hardware.camera2.CameraMetadata;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static android.content.Context.CAMERA_SERVICE;
|
||||
import static com.arthenica.ffmpegkit.FFmpegKitConfig.TAG;
|
||||
|
||||
/**
|
||||
* <p>Helper class to detect camera devices that can be used in
|
||||
* <code>FFmpeg</code>/<code>FFprobe</code> commands.
|
||||
*/
|
||||
class CameraSupport {
|
||||
|
||||
/**
|
||||
* <p>Lists camera ids that can be used in <code>FFmpeg</code>/<code>FFprobe</code> commands.
|
||||
*
|
||||
* @param context application context
|
||||
* @return the list of supported camera ids on Android API Level 24+, an empty list on older
|
||||
* API levels
|
||||
*/
|
||||
static List<String> extractSupportedCameraIds(final Context context) {
|
||||
final List<String> detectedCameraIdList = new ArrayList<>();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
try {
|
||||
final CameraManager manager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
|
||||
if (manager != null) {
|
||||
final String[] cameraIdList = manager.getCameraIdList();
|
||||
|
||||
for (String cameraId : cameraIdList) {
|
||||
final CameraCharacteristics chars = manager.getCameraCharacteristics(cameraId);
|
||||
final Integer cameraSupport = chars.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
|
||||
|
||||
if (cameraSupport != null && cameraSupport == CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
|
||||
Log.d(TAG, "Detected camera with id " + cameraId + " has LEGACY hardware level which is not supported by Android Camera2 NDK API.");
|
||||
} else if (cameraSupport != null) {
|
||||
detectedCameraIdList.add(cameraId);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (final CameraAccessException e) {
|
||||
Log.w(TAG, "Detecting camera ids failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
return detectedCameraIdList;
|
||||
}
|
||||
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2022 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class Chapter {
|
||||
|
||||
/* KEYS */
|
||||
public static final String KEY_ID = "id";
|
||||
public static final String KEY_TIME_BASE = "time_base";
|
||||
public static final String KEY_START = "start";
|
||||
public static final String KEY_START_TIME = "start_time";
|
||||
public static final String KEY_END = "end";
|
||||
public static final String KEY_END_TIME = "end_time";
|
||||
public static final String KEY_TAGS = "tags";
|
||||
|
||||
private final JSONObject jsonObject;
|
||||
|
||||
public Chapter(JSONObject jsonObject) {
|
||||
this.jsonObject = jsonObject;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return getNumberProperty(KEY_ID);
|
||||
}
|
||||
|
||||
public String getTimeBase() {
|
||||
return getStringProperty(KEY_TIME_BASE);
|
||||
}
|
||||
|
||||
public Long getStart() {
|
||||
return getNumberProperty(KEY_START);
|
||||
}
|
||||
|
||||
public String getStartTime() {
|
||||
return getStringProperty(KEY_START_TIME);
|
||||
}
|
||||
|
||||
public Long getEnd() {
|
||||
return getNumberProperty(KEY_END);
|
||||
}
|
||||
|
||||
public String getEndTime() {
|
||||
return getStringProperty(KEY_END_TIME);
|
||||
}
|
||||
|
||||
public JSONObject getTags() {
|
||||
return getProperty(KEY_TAGS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the chapter property associated with the key.
|
||||
*
|
||||
* @param key property key
|
||||
* @return chapter property as string or null if the key is not found
|
||||
*/
|
||||
public String getStringProperty(final String key) {
|
||||
JSONObject allProperties = getAllProperties();
|
||||
if (allProperties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (allProperties.has(key)) {
|
||||
return allProperties.optString(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the chapter property associated with the key.
|
||||
*
|
||||
* @param key property key
|
||||
* @return chapter property as Long or null if the key is not found
|
||||
*/
|
||||
public Long getNumberProperty(String key) {
|
||||
JSONObject allProperties = getAllProperties();
|
||||
if (allProperties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (allProperties.has(key)) {
|
||||
return allProperties.optLong(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the chapter property associated with the key.
|
||||
*
|
||||
* @param key property key
|
||||
* @return chapter property as a JSONObject or null if the key is not found
|
||||
*/
|
||||
public JSONObject getProperty(String key) {
|
||||
JSONObject allProperties = getAllProperties();
|
||||
if (allProperties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return allProperties.optJSONObject(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all chapter properties defined.
|
||||
*
|
||||
* @return all chapter properties as a JSONObject or null if no properties are defined
|
||||
*/
|
||||
public JSONObject getAllProperties() {
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
}
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
/**
|
||||
* <p>Main class to run <code>FFmpeg</code> commands. Supports executing commands both
|
||||
* synchronously and asynchronously.
|
||||
* <pre>
|
||||
* FFmpegSession session = FFmpegKit.execute("-i file1.mp4 -c:v libxvid file1.avi");
|
||||
*
|
||||
* FFmpegSession asyncSession = FFmpegKit.executeAsync("-i file1.mp4 -c:v libxvid file1.avi", completeCallback);
|
||||
* </pre>
|
||||
* <p>Provides overloaded <code>execute</code> methods to define session specific callbacks.
|
||||
* <pre>
|
||||
* FFmpegSession asyncSession = FFmpegKit.executeAsync("-i file1.mp4 -c:v libxvid file1.avi", completeCallback, logCallback, statisticsCallback);
|
||||
* </pre>
|
||||
*/
|
||||
public class FFmpegKit {
|
||||
|
||||
static {
|
||||
AbiDetect.class.getName();
|
||||
FFmpegKitConfig.class.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor hidden.
|
||||
*/
|
||||
private FFmpegKit() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Synchronously executes FFmpeg with arguments provided.
|
||||
*
|
||||
* @param arguments FFmpeg command options/arguments as string array
|
||||
* @return FFmpeg session created for this execution
|
||||
*/
|
||||
public static FFmpegSession executeWithArguments(final String[] arguments) {
|
||||
final FFmpegSession session = FFmpegSession.create(arguments);
|
||||
|
||||
FFmpegKitConfig.ffmpegExecute(session);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFmpeg execution with arguments provided.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFmpegSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param arguments FFmpeg command options/arguments as string array
|
||||
* @param completeCallback callback that will be called when the execution has completed
|
||||
* @return FFmpeg session created for this execution
|
||||
*/
|
||||
public static FFmpegSession executeWithArgumentsAsync(final String[] arguments,
|
||||
final FFmpegSessionCompleteCallback completeCallback) {
|
||||
final FFmpegSession session = FFmpegSession.create(arguments, completeCallback);
|
||||
|
||||
FFmpegKitConfig.asyncFFmpegExecute(session);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFmpeg execution with arguments provided.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFmpegSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param arguments FFmpeg command options/arguments as string array
|
||||
* @param completeCallback callback that will be called when the execution has completed
|
||||
* @param logCallback callback that will receive logs
|
||||
* @param statisticsCallback callback that will receive statistics
|
||||
* @return FFmpeg session created for this execution
|
||||
*/
|
||||
public static FFmpegSession executeWithArgumentsAsync(final String[] arguments,
|
||||
final FFmpegSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback,
|
||||
final StatisticsCallback statisticsCallback) {
|
||||
final FFmpegSession session = FFmpegSession.create(arguments, completeCallback, logCallback, statisticsCallback);
|
||||
|
||||
FFmpegKitConfig.asyncFFmpegExecute(session);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFmpeg execution with arguments provided.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFmpegSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param arguments FFmpeg command options/arguments as string array
|
||||
* @param completeCallback callback that will be called when the execution has completed
|
||||
* @param executorService executor service that will be used to run this asynchronous operation
|
||||
* @return FFmpeg session created for this execution
|
||||
*/
|
||||
public static FFmpegSession executeWithArgumentsAsync(final String[] arguments,
|
||||
final FFmpegSessionCompleteCallback completeCallback,
|
||||
final ExecutorService executorService) {
|
||||
final FFmpegSession session = FFmpegSession.create(arguments, completeCallback);
|
||||
|
||||
FFmpegKitConfig.asyncFFmpegExecute(session, executorService);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFmpeg execution with arguments provided.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFmpegSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param arguments FFmpeg command options/arguments as string array
|
||||
* @param completeCallback callback that will be called when the execution has completed
|
||||
* @param logCallback callback that will receive logs
|
||||
* @param statisticsCallback callback that will receive statistics
|
||||
* @param executorService executor service that will be used to run this asynchronous
|
||||
* operation
|
||||
* @return FFmpeg session created for this execution
|
||||
*/
|
||||
public static FFmpegSession executeWithArgumentsAsync(final String[] arguments,
|
||||
final FFmpegSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback,
|
||||
final StatisticsCallback statisticsCallback,
|
||||
final ExecutorService executorService) {
|
||||
final FFmpegSession session = FFmpegSession.create(arguments, completeCallback, logCallback, statisticsCallback);
|
||||
|
||||
FFmpegKitConfig.asyncFFmpegExecute(session, executorService);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Synchronously executes FFmpeg command provided. Space character is used to split command
|
||||
* into arguments. You can use single or double quote characters to specify arguments inside
|
||||
* your command.
|
||||
*
|
||||
* @param command FFmpeg command
|
||||
* @return FFmpeg session created for this execution
|
||||
*/
|
||||
public static FFmpegSession execute(final String command) {
|
||||
return executeWithArguments(FFmpegKitConfig.parseArguments(command));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFmpeg execution for the given command. Space character is used to
|
||||
* split the command into arguments. You can use single or double quote characters to specify
|
||||
* arguments inside your command.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFmpegSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param command FFmpeg command
|
||||
* @param completeCallback callback that will be called when the execution has completed
|
||||
* @return FFmpeg session created for this execution
|
||||
*/
|
||||
public static FFmpegSession executeAsync(final String command,
|
||||
final FFmpegSessionCompleteCallback completeCallback) {
|
||||
return executeWithArgumentsAsync(FFmpegKitConfig.parseArguments(command), completeCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFmpeg execution for the given command. Space character is used to
|
||||
* split the command into arguments. You can use single or double quote characters to specify
|
||||
* arguments inside your command.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFmpegSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param command FFmpeg command
|
||||
* @param completeCallback callback that will be called when the execution has completed
|
||||
* @param logCallback callback that will receive logs
|
||||
* @param statisticsCallback callback that will receive statistics
|
||||
* @return FFmpeg session created for this execution
|
||||
*/
|
||||
public static FFmpegSession executeAsync(final String command,
|
||||
final FFmpegSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback,
|
||||
final StatisticsCallback statisticsCallback) {
|
||||
return executeWithArgumentsAsync(FFmpegKitConfig.parseArguments(command), completeCallback, logCallback, statisticsCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFmpeg execution for the given command. Space character is used to
|
||||
* split the command into arguments. You can use single or double quote characters to specify
|
||||
* arguments inside your command.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFmpegSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param command FFmpeg command
|
||||
* @param completeCallback callback that will be called when the execution has completed
|
||||
* @param executorService executor service that will be used to run this asynchronous operation
|
||||
* @return FFmpeg session created for this execution
|
||||
*/
|
||||
public static FFmpegSession executeAsync(final String command,
|
||||
final FFmpegSessionCompleteCallback completeCallback,
|
||||
final ExecutorService executorService) {
|
||||
final FFmpegSession session = FFmpegSession.create(FFmpegKitConfig.parseArguments(command), completeCallback);
|
||||
|
||||
FFmpegKitConfig.asyncFFmpegExecute(session, executorService);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFmpeg execution for the given command. Space character is used to
|
||||
* split the command into arguments. You can use single or double quote characters to specify
|
||||
* arguments inside your command.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFmpegSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param command FFmpeg command
|
||||
* @param completeCallback callback that will be called when the execution has completed
|
||||
* @param logCallback callback that will receive logs
|
||||
* @param statisticsCallback callback that will receive statistics
|
||||
* @param executorService executor service that will be used to run this asynchronous operation
|
||||
* @return FFmpeg session created for this execution
|
||||
*/
|
||||
public static FFmpegSession executeAsync(final String command,
|
||||
final FFmpegSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback,
|
||||
final StatisticsCallback statisticsCallback,
|
||||
final ExecutorService executorService) {
|
||||
final FFmpegSession session = FFmpegSession.create(FFmpegKitConfig.parseArguments(command), completeCallback, logCallback, statisticsCallback);
|
||||
|
||||
FFmpegKitConfig.asyncFFmpegExecute(session, executorService);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Cancels all running sessions.
|
||||
*
|
||||
* <p>This method does not wait for termination to complete and returns immediately.
|
||||
*/
|
||||
public static void cancel() {
|
||||
|
||||
/*
|
||||
* ZERO (0) IS A SPECIAL SESSION ID
|
||||
* WHEN IT IS PASSED TO THIS METHOD, A SIGINT IS GENERATED WHICH CANCELS ALL ONGOING
|
||||
* SESSIONS
|
||||
*/
|
||||
FFmpegKitConfig.nativeFFmpegCancel(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Cancels the session specified with <code>sessionId</code>.
|
||||
*
|
||||
* <p>This method does not wait for termination to complete and returns immediately.
|
||||
*
|
||||
* @param sessionId id of the session that will be cancelled
|
||||
*/
|
||||
public static void cancel(final long sessionId) {
|
||||
FFmpegKitConfig.nativeFFmpegCancel(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Lists all FFmpeg sessions in the session history.
|
||||
*
|
||||
* @return all FFmpeg sessions in the session history
|
||||
*/
|
||||
public static List<FFmpegSession> listSessions() {
|
||||
return FFmpegKitConfig.getFFmpegSessions();
|
||||
}
|
||||
|
||||
}
|
||||
+1500
File diff suppressed because it is too large
Load Diff
+260
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2021 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>An FFmpeg session.
|
||||
*/
|
||||
public class FFmpegSession extends AbstractSession implements Session {
|
||||
|
||||
/**
|
||||
* Session specific statistics callback.
|
||||
*/
|
||||
private final StatisticsCallback statisticsCallback;
|
||||
|
||||
/**
|
||||
* Session specific complete callback.
|
||||
*/
|
||||
private final FFmpegSessionCompleteCallback completeCallback;
|
||||
|
||||
/**
|
||||
* Statistics entries received for this session.
|
||||
*/
|
||||
private final List<Statistics> statistics;
|
||||
|
||||
/**
|
||||
* Statistics entry lock.
|
||||
*/
|
||||
private final Object statisticsLock;
|
||||
|
||||
/**
|
||||
* Builds a new FFmpeg session.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @return created session
|
||||
*/
|
||||
public static FFmpegSession create(final String[] arguments) {
|
||||
return new FFmpegSession(arguments, null, null, null, FFmpegKitConfig.getLogRedirectionStrategy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new FFmpeg session.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @param completeCallback session specific complete callback
|
||||
* @return created session
|
||||
*/
|
||||
public static FFmpegSession create(final String[] arguments, final FFmpegSessionCompleteCallback completeCallback) {
|
||||
return new FFmpegSession(arguments, completeCallback, null, null, FFmpegKitConfig.getLogRedirectionStrategy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new FFmpeg session.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @param completeCallback session specific complete callback
|
||||
* @param logCallback session specific log callback
|
||||
* @param statisticsCallback session specific statistics callback
|
||||
* @return created session
|
||||
*/
|
||||
public static FFmpegSession create(final String[] arguments,
|
||||
final FFmpegSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback,
|
||||
final StatisticsCallback statisticsCallback) {
|
||||
return new FFmpegSession(arguments, completeCallback, logCallback, statisticsCallback, FFmpegKitConfig.getLogRedirectionStrategy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new FFmpeg session.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @param completeCallback session specific complete callback
|
||||
* @param logCallback session specific log callback
|
||||
* @param statisticsCallback session specific statistics callback
|
||||
* @param logRedirectionStrategy session specific log redirection strategy
|
||||
* @return created session
|
||||
*/
|
||||
public static FFmpegSession create(final String[] arguments,
|
||||
final FFmpegSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback,
|
||||
final StatisticsCallback statisticsCallback,
|
||||
final LogRedirectionStrategy logRedirectionStrategy) {
|
||||
return new FFmpegSession(arguments, completeCallback, logCallback, statisticsCallback, logRedirectionStrategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new FFmpeg session.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @param completeCallback session specific complete callback
|
||||
* @param logCallback session specific log callback
|
||||
* @param statisticsCallback session specific statistics callback
|
||||
* @param logRedirectionStrategy session specific log redirection strategy
|
||||
*/
|
||||
private FFmpegSession(final String[] arguments,
|
||||
final FFmpegSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback,
|
||||
final StatisticsCallback statisticsCallback,
|
||||
final LogRedirectionStrategy logRedirectionStrategy) {
|
||||
super(arguments, logCallback, logRedirectionStrategy);
|
||||
|
||||
this.completeCallback = completeCallback;
|
||||
this.statisticsCallback = statisticsCallback;
|
||||
|
||||
this.statistics = new LinkedList<>();
|
||||
this.statisticsLock = new Object();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the session specific statistics callback.
|
||||
*
|
||||
* @return session specific statistics callback
|
||||
*/
|
||||
public StatisticsCallback getStatisticsCallback() {
|
||||
return statisticsCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the session specific complete callback.
|
||||
*
|
||||
* @return session specific complete callback
|
||||
*/
|
||||
public FFmpegSessionCompleteCallback getCompleteCallback() {
|
||||
return completeCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all statistics entries generated for this session. If there are asynchronous
|
||||
* messages that are not delivered yet, this method waits for them until the given timeout.
|
||||
*
|
||||
* @param waitTimeout wait timeout for asynchronous messages in milliseconds
|
||||
* @return list of statistics entries generated for this session
|
||||
*/
|
||||
public List<Statistics> getAllStatistics(final int waitTimeout) {
|
||||
waitForAsynchronousMessagesInTransmit(waitTimeout);
|
||||
|
||||
if (thereAreAsynchronousMessagesInTransmit()) {
|
||||
android.util.Log.i(FFmpegKitConfig.TAG, String.format("getAllStatistics was called to return all statistics but there are still statistics being transmitted for session id %d.", sessionId));
|
||||
}
|
||||
|
||||
return getStatistics();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all statistics entries generated for this session. If there are asynchronous
|
||||
* messages that are not delivered yet, this method waits for them until
|
||||
* {@link #DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT} expires.
|
||||
*
|
||||
* @return list of statistics entries generated for this session
|
||||
*/
|
||||
public List<Statistics> getAllStatistics() {
|
||||
return getAllStatistics(DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all statistics entries delivered for this session. Note that if there are
|
||||
* asynchronous messages that are not delivered yet, this method will not wait for
|
||||
* them and will return immediately.
|
||||
*
|
||||
* @return list of statistics entries received for this session
|
||||
*/
|
||||
public List<Statistics> getStatistics() {
|
||||
synchronized (statisticsLock) {
|
||||
return statistics;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last received statistics entry.
|
||||
*
|
||||
* @return the last received statistics entry or null if there are not any statistics entries
|
||||
* received
|
||||
*/
|
||||
public Statistics getLastReceivedStatistics() {
|
||||
synchronized (statisticsLock) {
|
||||
if (statistics.size() > 0) {
|
||||
return statistics.get(statistics.size() - 1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new statistics entry for this session. It is invoked internally by
|
||||
* <code>FFmpegKit</code> library methods. Must not be used by user applications.
|
||||
*
|
||||
* @param statistics statistics entry
|
||||
*/
|
||||
public void addStatistics(final Statistics statistics) {
|
||||
synchronized (statisticsLock) {
|
||||
this.statistics.add(statistics);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFFmpeg() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFFprobe() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMediaInformation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
stringBuilder.append("FFmpegSession{");
|
||||
stringBuilder.append("sessionId=");
|
||||
stringBuilder.append(sessionId);
|
||||
stringBuilder.append(", createTime=");
|
||||
stringBuilder.append(createTime);
|
||||
stringBuilder.append(", startTime=");
|
||||
stringBuilder.append(startTime);
|
||||
stringBuilder.append(", endTime=");
|
||||
stringBuilder.append(endTime);
|
||||
stringBuilder.append(", arguments=");
|
||||
stringBuilder.append(FFmpegKitConfig.argumentsToString(arguments));
|
||||
stringBuilder.append(", logs=");
|
||||
stringBuilder.append(getLogsAsString());
|
||||
stringBuilder.append(", state=");
|
||||
stringBuilder.append(state);
|
||||
stringBuilder.append(", returnCode=");
|
||||
stringBuilder.append(returnCode);
|
||||
stringBuilder.append(", failStackTrace=");
|
||||
stringBuilder.append('\'');
|
||||
stringBuilder.append(failStackTrace);
|
||||
stringBuilder.append('\'');
|
||||
stringBuilder.append('}');
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
/**
|
||||
* <p>Callback function that is invoked when an asynchronous <code>FFmpeg</code> session has ended.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface FFmpegSessionCompleteCallback {
|
||||
|
||||
/**
|
||||
* <p>Called when an FFmpeg session has ended.
|
||||
*
|
||||
* @param session FFmpeg session
|
||||
*/
|
||||
void apply(final FFmpegSession session);
|
||||
|
||||
}
|
||||
+475
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2021 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
/**
|
||||
* <p>Main class to run <code>FFprobe</code> commands. Supports executing commands both
|
||||
* synchronously and asynchronously.
|
||||
* <pre>
|
||||
* FFprobeSession session = FFprobeKit.execute("-hide_banner -v error -show_entries format=size -of default=noprint_wrappers=1 file1.mp4");
|
||||
*
|
||||
* FFprobeSession asyncSession = FFprobeKit.executeAsync("-hide_banner -v error -show_entries format=size -of default=noprint_wrappers=1 file1.mp4", completeCallback);
|
||||
* </pre>
|
||||
* <p>Provides overloaded <code>execute</code> methods to define session specific callbacks.
|
||||
* <pre>
|
||||
* FFprobeSession session = FFprobeKit.executeAsync("-hide_banner -v error -show_entries format=size -of default=noprint_wrappers=1 file1.mp4", completeCallback, logCallback);
|
||||
* </pre>
|
||||
* <p>It can extract media information for a file or a url, using {@link #getMediaInformation(String)} method.
|
||||
* <pre>
|
||||
* MediaInformationSession session = FFprobeKit.getMediaInformation("file1.mp4");
|
||||
* </pre>
|
||||
*/
|
||||
public class FFprobeKit {
|
||||
|
||||
static {
|
||||
AbiDetect.class.getName();
|
||||
FFmpegKitConfig.class.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor hidden.
|
||||
*/
|
||||
private FFprobeKit() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Builds the default command used to get media information for a file.
|
||||
*
|
||||
* @param path file path to use in the command
|
||||
* @return default command arguments to get media information
|
||||
*/
|
||||
private static String[] defaultGetMediaInformationCommandArguments(final String path) {
|
||||
return new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-show_chapters", "-i", path};
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Synchronously executes FFprobe with arguments provided.
|
||||
*
|
||||
* @param arguments FFprobe command options/arguments as string array
|
||||
* @return FFprobe session created for this execution
|
||||
*/
|
||||
public static FFprobeSession executeWithArguments(final String[] arguments) {
|
||||
final FFprobeSession session = FFprobeSession.create(arguments);
|
||||
|
||||
FFmpegKitConfig.ffprobeExecute(session);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFprobe execution with arguments provided.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFprobeSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param arguments FFprobe command options/arguments as string array
|
||||
* @param completeCallback callback that will be called when the execution has completed
|
||||
* @return FFprobe session created for this execution
|
||||
*/
|
||||
public static FFprobeSession executeWithArgumentsAsync(final String[] arguments,
|
||||
final FFprobeSessionCompleteCallback completeCallback) {
|
||||
final FFprobeSession session = FFprobeSession.create(arguments, completeCallback);
|
||||
|
||||
FFmpegKitConfig.asyncFFprobeExecute(session);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFprobe execution with arguments provided.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFprobeSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param arguments FFprobe command options/arguments as string array
|
||||
* @param completeCallback callback that will be notified when execution has completed
|
||||
* @param logCallback callback that will receive logs
|
||||
* @return FFprobe session created for this execution
|
||||
*/
|
||||
public static FFprobeSession executeWithArgumentsAsync(final String[] arguments,
|
||||
final FFprobeSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback) {
|
||||
final FFprobeSession session = FFprobeSession.create(arguments, completeCallback, logCallback);
|
||||
|
||||
FFmpegKitConfig.asyncFFprobeExecute(session);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFprobe execution with arguments provided.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFprobeSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param arguments FFprobe command options/arguments as string array
|
||||
* @param completeCallback callback that will be called when the execution has completed
|
||||
* @param executorService executor service that will be used to run this asynchronous operation
|
||||
* @return FFprobe session created for this execution
|
||||
*/
|
||||
public static FFprobeSession executeWithArgumentsAsync(final String[] arguments,
|
||||
final FFprobeSessionCompleteCallback completeCallback,
|
||||
final ExecutorService executorService) {
|
||||
final FFprobeSession session = FFprobeSession.create(arguments, completeCallback);
|
||||
|
||||
FFmpegKitConfig.asyncFFprobeExecute(session, executorService);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFprobe execution with arguments provided.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFprobeSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param arguments FFprobe command options/arguments as string array
|
||||
* @param completeCallback callback that will be notified when execution has completed
|
||||
* @param logCallback callback that will receive logs
|
||||
* @param executorService executor service that will be used to run this asynchronous operation
|
||||
* @return FFprobe session created for this execution
|
||||
*/
|
||||
public static FFprobeSession executeWithArgumentsAsync(final String[] arguments,
|
||||
final FFprobeSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback,
|
||||
final ExecutorService executorService) {
|
||||
final FFprobeSession session = FFprobeSession.create(arguments, completeCallback, logCallback);
|
||||
|
||||
FFmpegKitConfig.asyncFFprobeExecute(session, executorService);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Synchronously executes FFprobe command provided. Space character is used to split command
|
||||
* into arguments. You can use single or double quote characters to specify arguments inside
|
||||
* your command.
|
||||
*
|
||||
* @param command FFprobe command
|
||||
* @return FFprobe session created for this execution
|
||||
*/
|
||||
public static FFprobeSession execute(final String command) {
|
||||
return executeWithArguments(FFmpegKitConfig.parseArguments(command));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFprobe execution for the given command. Space character is used
|
||||
* to split the command into arguments. You can use single or double quote characters to
|
||||
* specify arguments inside your command.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFprobeSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param command FFprobe command
|
||||
* @param completeCallback callback that will be called when the execution has completed
|
||||
* @return FFprobe session created for this execution
|
||||
*/
|
||||
public static FFprobeSession executeAsync(final String command,
|
||||
final FFprobeSessionCompleteCallback completeCallback) {
|
||||
return executeWithArgumentsAsync(FFmpegKitConfig.parseArguments(command), completeCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFprobe execution for the given command. Space character is used
|
||||
* to split the command into arguments. You can use single or double quote characters to
|
||||
* specify arguments inside your command.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFprobeSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param command FFprobe command
|
||||
* @param completeCallback callback that will be notified when execution has completed
|
||||
* @param logCallback callback that will receive logs
|
||||
* @return FFprobe session created for this execution
|
||||
*/
|
||||
public static FFprobeSession executeAsync(final String command,
|
||||
final FFprobeSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback) {
|
||||
return executeWithArgumentsAsync(FFmpegKitConfig.parseArguments(command), completeCallback, logCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFprobe execution for the given command. Space character is used
|
||||
* to split the command into arguments. You can use single or double quote characters to
|
||||
* specify arguments inside your command.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFprobeSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param command FFprobe command
|
||||
* @param completeCallback callback that will be called when the execution has completed
|
||||
* @param executorService executor service that will be used to run this asynchronous operation
|
||||
* @return FFprobe session created for this execution
|
||||
*/
|
||||
public static FFprobeSession executeAsync(final String command,
|
||||
final FFprobeSessionCompleteCallback completeCallback,
|
||||
final ExecutorService executorService) {
|
||||
final FFprobeSession session = FFprobeSession.create(FFmpegKitConfig.parseArguments(command), completeCallback);
|
||||
|
||||
FFmpegKitConfig.asyncFFprobeExecute(session, executorService);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFprobe execution for the given command. Space character is used
|
||||
* to split the command into arguments. You can use single or double quote characters to
|
||||
* specify arguments inside your command.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use an {@link FFprobeSessionCompleteCallback} if you want to be notified about the
|
||||
* result.
|
||||
*
|
||||
* @param command FFprobe command
|
||||
* @param completeCallback callback that will be called when the execution has completed
|
||||
* @param logCallback callback that will receive logs
|
||||
* @param executorService executor service that will be used to run this asynchronous operation
|
||||
* @return FFprobe session created for this execution
|
||||
*/
|
||||
public static FFprobeSession executeAsync(final String command,
|
||||
final FFprobeSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback,
|
||||
final ExecutorService executorService) {
|
||||
final FFprobeSession session = FFprobeSession.create(FFmpegKitConfig.parseArguments(command), completeCallback, logCallback);
|
||||
|
||||
FFmpegKitConfig.asyncFFprobeExecute(session, executorService);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Extracts media information for the file specified with path.
|
||||
*
|
||||
* @param path path or uri of a media file
|
||||
* @return media information session created for this execution
|
||||
*/
|
||||
public static MediaInformationSession getMediaInformation(final String path) {
|
||||
final MediaInformationSession session = MediaInformationSession.create(defaultGetMediaInformationCommandArguments(path));
|
||||
|
||||
FFmpegKitConfig.getMediaInformationExecute(session, AbstractSession.DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Extracts media information for the file specified with path.
|
||||
*
|
||||
* @param path path or uri of a media file
|
||||
* @param waitTimeout max time to wait until media information is transmitted
|
||||
* @return media information session created for this execution
|
||||
*/
|
||||
public static MediaInformationSession getMediaInformation(final String path,
|
||||
final int waitTimeout) {
|
||||
final MediaInformationSession session = MediaInformationSession.create(defaultGetMediaInformationCommandArguments(path));
|
||||
|
||||
FFmpegKitConfig.getMediaInformationExecute(session, waitTimeout);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFprobe execution to extract the media information for the
|
||||
* specified file.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use a {@link MediaInformationSessionCompleteCallback} if you want to be notified
|
||||
* about the result.
|
||||
*
|
||||
* @param path path or uri of a media file
|
||||
* @param completeCallback callback that will be called when the execution has completed
|
||||
* @return media information session created for this execution
|
||||
*/
|
||||
public static MediaInformationSession getMediaInformationAsync(final String path,
|
||||
final MediaInformationSessionCompleteCallback completeCallback) {
|
||||
final MediaInformationSession session = MediaInformationSession.create(defaultGetMediaInformationCommandArguments(path), completeCallback);
|
||||
|
||||
FFmpegKitConfig.asyncGetMediaInformationExecute(session, AbstractSession.DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFprobe execution to extract the media information for the
|
||||
* specified file.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use a {@link MediaInformationSessionCompleteCallback} if you want to be notified
|
||||
* about the result.
|
||||
*
|
||||
* @param path path or uri of a media file
|
||||
* @param completeCallback callback that will be notified when execution has completed
|
||||
* @param logCallback callback that will receive logs
|
||||
* @param waitTimeout max time to wait until media information is transmitted
|
||||
* @return media information session created for this execution
|
||||
*/
|
||||
public static MediaInformationSession getMediaInformationAsync(final String path,
|
||||
final MediaInformationSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback,
|
||||
final int waitTimeout) {
|
||||
final MediaInformationSession session = MediaInformationSession.create(defaultGetMediaInformationCommandArguments(path), completeCallback, logCallback);
|
||||
|
||||
FFmpegKitConfig.asyncGetMediaInformationExecute(session, waitTimeout);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFprobe execution to extract the media information for the
|
||||
* specified file.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use a {@link MediaInformationSessionCompleteCallback} if you want to be notified
|
||||
* about the result.
|
||||
*
|
||||
* @param path path or uri of a media file
|
||||
* @param completeCallback callback that will be called when the execution has completed
|
||||
* @param executorService executor service that will be used to run this asynchronous operation
|
||||
* @return media information session created for this execution
|
||||
*/
|
||||
public static MediaInformationSession getMediaInformationAsync(final String path,
|
||||
final MediaInformationSessionCompleteCallback completeCallback,
|
||||
final ExecutorService executorService) {
|
||||
final MediaInformationSession session = MediaInformationSession.create(defaultGetMediaInformationCommandArguments(path), completeCallback);
|
||||
|
||||
FFmpegKitConfig.asyncGetMediaInformationExecute(session, executorService, AbstractSession.DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFprobe execution to extract the media information for the
|
||||
* specified file.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use a {@link MediaInformationSessionCompleteCallback} if you want to be notified
|
||||
* about the result.
|
||||
*
|
||||
* @param path path or uri of a media file
|
||||
* @param completeCallback callback that will be notified when execution has completed
|
||||
* @param logCallback callback that will receive logs
|
||||
* @param executorService executor service that will be used to run this asynchronous operation
|
||||
* @param waitTimeout max time to wait until media information is transmitted
|
||||
* @return media information session created for this execution
|
||||
*/
|
||||
public static MediaInformationSession getMediaInformationAsync(final String path,
|
||||
final MediaInformationSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback,
|
||||
final ExecutorService executorService,
|
||||
final int waitTimeout) {
|
||||
final MediaInformationSession session = MediaInformationSession.create(defaultGetMediaInformationCommandArguments(path), completeCallback, logCallback);
|
||||
|
||||
FFmpegKitConfig.asyncGetMediaInformationExecute(session, executorService, waitTimeout);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Extracts media information using the command provided.
|
||||
*
|
||||
* @param command FFprobe command that prints media information for a file in JSON format
|
||||
* @return media information session created for this execution
|
||||
*/
|
||||
public static MediaInformationSession getMediaInformationFromCommand(final String command) {
|
||||
final MediaInformationSession session = MediaInformationSession.create(FFmpegKitConfig.parseArguments(command));
|
||||
|
||||
FFmpegKitConfig.getMediaInformationExecute(session, AbstractSession.DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFprobe execution to extract media information using a command.
|
||||
* The command passed to this method must generate the output in JSON format in order to
|
||||
* successfully extract media information from it.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use a {@link MediaInformationSessionCompleteCallback} if you want to be notified
|
||||
* about the result.
|
||||
*
|
||||
* @param command FFprobe command that prints media information for a file in JSON
|
||||
* format
|
||||
* @param completeCallback callback that will be notified when execution has completed
|
||||
* @param logCallback callback that will receive logs
|
||||
* @param waitTimeout max time to wait until media information is transmitted
|
||||
* @return media information session created for this execution
|
||||
*/
|
||||
public static MediaInformationSession getMediaInformationFromCommandAsync(final String command,
|
||||
final MediaInformationSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback,
|
||||
final int waitTimeout) {
|
||||
return getMediaInformationFromCommandArgumentsAsync(FFmpegKitConfig.parseArguments(command), completeCallback, logCallback, waitTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous FFprobe execution to extract media information using command
|
||||
* arguments. The command passed to this method must generate the output in JSON format in
|
||||
* order to successfully extract media information from it.
|
||||
*
|
||||
* <p>Note that this method returns immediately and does not wait the execution to complete.
|
||||
* You must use a {@link MediaInformationSessionCompleteCallback} if you want to be notified
|
||||
* about the result.
|
||||
*
|
||||
* @param arguments FFprobe command arguments that print media information for a file in
|
||||
* JSON format
|
||||
* @param completeCallback callback that will be notified when execution has completed
|
||||
* @param logCallback callback that will receive logs
|
||||
* @param waitTimeout max time to wait until media information is transmitted
|
||||
* @return media information session created for this execution
|
||||
*/
|
||||
private static MediaInformationSession getMediaInformationFromCommandArgumentsAsync(final String[] arguments,
|
||||
final MediaInformationSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback,
|
||||
final int waitTimeout) {
|
||||
final MediaInformationSession session = MediaInformationSession.create(arguments, completeCallback, logCallback);
|
||||
|
||||
FFmpegKitConfig.asyncGetMediaInformationExecute(session, waitTimeout);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Lists all FFprobe sessions in the session history.
|
||||
*
|
||||
* @return all FFprobe sessions in the session history
|
||||
*/
|
||||
public static List<FFprobeSession> listFFprobeSessions() {
|
||||
return FFmpegKitConfig.getFFprobeSessions();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Lists all MediaInformation sessions in the session history.
|
||||
*
|
||||
* @return all MediaInformation sessions in the session history
|
||||
*/
|
||||
public static List<MediaInformationSession> listMediaInformationSessions() {
|
||||
return FFmpegKitConfig.getMediaInformationSessions();
|
||||
}
|
||||
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2022 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
/**
|
||||
* <p>An FFprobe session.
|
||||
*/
|
||||
public class FFprobeSession extends AbstractSession implements Session {
|
||||
|
||||
/**
|
||||
* Session specific complete callback.
|
||||
*/
|
||||
private final FFprobeSessionCompleteCallback completeCallback;
|
||||
|
||||
/**
|
||||
* Builds a new FFprobe session.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @return created session
|
||||
*/
|
||||
public static FFprobeSession create(final String[] arguments) {
|
||||
return new FFprobeSession(arguments, null, null, FFmpegKitConfig.getLogRedirectionStrategy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new FFprobe session.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @param completeCallback session specific complete callback
|
||||
* @return created session
|
||||
*/
|
||||
public static FFprobeSession create(final String[] arguments, final FFprobeSessionCompleteCallback completeCallback) {
|
||||
return new FFprobeSession(arguments, completeCallback, null, FFmpegKitConfig.getLogRedirectionStrategy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new FFprobe session.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @param completeCallback session specific complete callback
|
||||
* @param logCallback session specific log callback
|
||||
* @return created session
|
||||
*/
|
||||
public static FFprobeSession create(final String[] arguments,
|
||||
final FFprobeSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback) {
|
||||
return new FFprobeSession(arguments, completeCallback, logCallback, FFmpegKitConfig.getLogRedirectionStrategy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new FFprobe session.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @param completeCallback session specific complete callback
|
||||
* @param logCallback session specific log callback
|
||||
* @param logRedirectionStrategy session specific log redirection strategy
|
||||
* @return created session
|
||||
*/
|
||||
public static FFprobeSession create(final String[] arguments,
|
||||
final FFprobeSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback,
|
||||
final LogRedirectionStrategy logRedirectionStrategy) {
|
||||
return new FFprobeSession(arguments, completeCallback, logCallback, logRedirectionStrategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new FFprobe session.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @param completeCallback session specific complete callback
|
||||
* @param logCallback session specific log callback
|
||||
* @param logRedirectionStrategy session specific log redirection strategy
|
||||
*/
|
||||
private FFprobeSession(final String[] arguments,
|
||||
final FFprobeSessionCompleteCallback completeCallback,
|
||||
final LogCallback logCallback,
|
||||
final LogRedirectionStrategy logRedirectionStrategy) {
|
||||
super(arguments, logCallback, logRedirectionStrategy);
|
||||
|
||||
this.completeCallback = completeCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the session specific complete callback.
|
||||
*
|
||||
* @return session specific complete callback
|
||||
*/
|
||||
public FFprobeSessionCompleteCallback getCompleteCallback() {
|
||||
return completeCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFFmpeg() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFFprobe() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMediaInformation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
stringBuilder.append("FFprobeSession{");
|
||||
stringBuilder.append("sessionId=");
|
||||
stringBuilder.append(sessionId);
|
||||
stringBuilder.append(", createTime=");
|
||||
stringBuilder.append(createTime);
|
||||
stringBuilder.append(", startTime=");
|
||||
stringBuilder.append(startTime);
|
||||
stringBuilder.append(", endTime=");
|
||||
stringBuilder.append(endTime);
|
||||
stringBuilder.append(", arguments=");
|
||||
stringBuilder.append(FFmpegKitConfig.argumentsToString(arguments));
|
||||
stringBuilder.append(", logs=");
|
||||
stringBuilder.append(getLogsAsString());
|
||||
stringBuilder.append(", state=");
|
||||
stringBuilder.append(state);
|
||||
stringBuilder.append(", returnCode=");
|
||||
stringBuilder.append(returnCode);
|
||||
stringBuilder.append(", failStackTrace=");
|
||||
stringBuilder.append('\'');
|
||||
stringBuilder.append(failStackTrace);
|
||||
stringBuilder.append('\'');
|
||||
stringBuilder.append('}');
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
/**
|
||||
* <p>Callback function that is invoked when an asynchronous <code>FFprobe</code> session has ended.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface FFprobeSessionCompleteCallback {
|
||||
|
||||
/**
|
||||
* <p>Called when an FFprobe session has ended.
|
||||
*
|
||||
* @param session FFprobe session
|
||||
*/
|
||||
void apply(final FFprobeSession session);
|
||||
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
/**
|
||||
* <p>Enumeration type for log levels.
|
||||
*/
|
||||
public enum Level {
|
||||
|
||||
/**
|
||||
* This log level is defined by FFmpegKit. It is used to specify logs printed to stderr by
|
||||
* FFmpeg. Logs that has this level are not filtered and always redirected.
|
||||
*/
|
||||
AV_LOG_STDERR(-16),
|
||||
|
||||
/**
|
||||
* Print no output.
|
||||
*/
|
||||
AV_LOG_QUIET(-8),
|
||||
|
||||
/**
|
||||
* Something went really wrong and we will crash now.
|
||||
*/
|
||||
AV_LOG_PANIC(0),
|
||||
|
||||
/**
|
||||
* Something went wrong and recovery is not possible.
|
||||
* For example, no header was found for a format which depends
|
||||
* on headers or an illegal combination of parameters is used.
|
||||
*/
|
||||
AV_LOG_FATAL(8),
|
||||
|
||||
/**
|
||||
* Something went wrong and cannot losslessly be recovered.
|
||||
* However, not all future data is affected.
|
||||
*/
|
||||
AV_LOG_ERROR(16),
|
||||
|
||||
/**
|
||||
* Something somehow does not look correct. This may or may not
|
||||
* lead to problems. An example would be the use of '-vstrict -2'.
|
||||
*/
|
||||
AV_LOG_WARNING(24),
|
||||
|
||||
/**
|
||||
* Standard information.
|
||||
*/
|
||||
AV_LOG_INFO(32),
|
||||
|
||||
/**
|
||||
* Detailed information.
|
||||
*/
|
||||
AV_LOG_VERBOSE(40),
|
||||
|
||||
/**
|
||||
* Stuff which is only useful for libav* developers.
|
||||
*/
|
||||
AV_LOG_DEBUG(48),
|
||||
|
||||
/**
|
||||
* Extremely verbose debugging, useful for libav* development.
|
||||
*/
|
||||
AV_LOG_TRACE(56);
|
||||
|
||||
private final int value;
|
||||
|
||||
/**
|
||||
* <p>Returns the enumeration defined by provided value.
|
||||
*
|
||||
* @param value level value
|
||||
* @return enumeration defined by value
|
||||
*/
|
||||
public static Level from(final int value) {
|
||||
if (value == AV_LOG_STDERR.getValue()) {
|
||||
return AV_LOG_STDERR;
|
||||
} else if (value == AV_LOG_QUIET.getValue()) {
|
||||
return AV_LOG_QUIET;
|
||||
} else if (value == AV_LOG_PANIC.getValue()) {
|
||||
return AV_LOG_PANIC;
|
||||
} else if (value == AV_LOG_FATAL.getValue()) {
|
||||
return AV_LOG_FATAL;
|
||||
} else if (value == AV_LOG_ERROR.getValue()) {
|
||||
return AV_LOG_ERROR;
|
||||
} else if (value == AV_LOG_WARNING.getValue()) {
|
||||
return AV_LOG_WARNING;
|
||||
} else if (value == AV_LOG_INFO.getValue()) {
|
||||
return AV_LOG_INFO;
|
||||
} else if (value == AV_LOG_VERBOSE.getValue()) {
|
||||
return AV_LOG_VERBOSE;
|
||||
} else if (value == AV_LOG_DEBUG.getValue()) {
|
||||
return AV_LOG_DEBUG;
|
||||
} else {
|
||||
return AV_LOG_TRACE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns level value.
|
||||
*
|
||||
* @return level value
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new enum.
|
||||
*
|
||||
* @param value level value
|
||||
*/
|
||||
Level(final int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
/**
|
||||
* <p>Log entry for an <code>FFmpegKit</code> session.
|
||||
*/
|
||||
public class Log {
|
||||
private final long sessionId;
|
||||
private final Level level;
|
||||
private final String message;
|
||||
|
||||
public Log(final long sessionId, final Level level, final String message) {
|
||||
this.sessionId = sessionId;
|
||||
this.level = level;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public long getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public Level getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
stringBuilder.append("Log{");
|
||||
stringBuilder.append("sessionId=");
|
||||
stringBuilder.append(sessionId);
|
||||
stringBuilder.append(", level=");
|
||||
stringBuilder.append(level);
|
||||
stringBuilder.append(", message=");
|
||||
stringBuilder.append("\'");
|
||||
stringBuilder.append(message);
|
||||
stringBuilder.append('\'');
|
||||
stringBuilder.append('}');
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
/**
|
||||
* <p>Callback function that receives logs generated for <code>FFmpegKit</code> sessions.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface LogCallback {
|
||||
|
||||
/**
|
||||
* <p>Called when a log entry is received.
|
||||
*
|
||||
* @param log log entry
|
||||
*/
|
||||
void apply(final Log log);
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2021 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
public enum LogRedirectionStrategy {
|
||||
ALWAYS_PRINT_LOGS,
|
||||
PRINT_LOGS_WHEN_NO_CALLBACKS_DEFINED,
|
||||
PRINT_LOGS_WHEN_GLOBAL_CALLBACK_NOT_DEFINED,
|
||||
PRINT_LOGS_WHEN_SESSION_CALLBACK_NOT_DEFINED,
|
||||
NEVER_PRINT_LOGS
|
||||
}
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2022 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Media information class.
|
||||
*/
|
||||
public class MediaInformation {
|
||||
|
||||
/* COMMON KEYS */
|
||||
public static final String KEY_FORMAT_PROPERTIES = "format";
|
||||
public static final String KEY_FILENAME = "filename";
|
||||
public static final String KEY_FORMAT = "format_name";
|
||||
public static final String KEY_FORMAT_LONG = "format_long_name";
|
||||
public static final String KEY_START_TIME = "start_time";
|
||||
public static final String KEY_DURATION = "duration";
|
||||
public static final String KEY_SIZE = "size";
|
||||
public static final String KEY_BIT_RATE = "bit_rate";
|
||||
public static final String KEY_TAGS = "tags";
|
||||
|
||||
/**
|
||||
* Stores all properties.
|
||||
*/
|
||||
private final JSONObject jsonObject;
|
||||
|
||||
/**
|
||||
* Stores streams.
|
||||
*/
|
||||
private final List<StreamInformation> streams;
|
||||
|
||||
/**
|
||||
* Stores chapters.
|
||||
*/
|
||||
private final List<Chapter> chapters;
|
||||
|
||||
public MediaInformation(final JSONObject jsonObject, final List<StreamInformation> streams, final List<Chapter> chapters) {
|
||||
this.jsonObject = jsonObject;
|
||||
this.streams = streams;
|
||||
this.chapters = chapters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns file name.
|
||||
*
|
||||
* @return media file name
|
||||
*/
|
||||
public String getFilename() {
|
||||
return getStringFormatProperty(KEY_FILENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns format.
|
||||
*
|
||||
* @return media format
|
||||
*/
|
||||
public String getFormat() {
|
||||
return getStringFormatProperty(KEY_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns long format.
|
||||
*
|
||||
* @return media long format
|
||||
*/
|
||||
public String getLongFormat() {
|
||||
return getStringFormatProperty(KEY_FORMAT_LONG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns duration.
|
||||
*
|
||||
* @return media duration in "seconds.microseconds" format
|
||||
*/
|
||||
public String getDuration() {
|
||||
return getStringFormatProperty(KEY_DURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns start time.
|
||||
*
|
||||
* @return media start time in milliseconds
|
||||
*/
|
||||
public String getStartTime() {
|
||||
return getStringFormatProperty(KEY_START_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns size.
|
||||
*
|
||||
* @return media size in bytes
|
||||
*/
|
||||
public String getSize() {
|
||||
return getStringFormatProperty(KEY_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bitrate.
|
||||
*
|
||||
* @return media bitrate in kb/s
|
||||
*/
|
||||
public String getBitrate() {
|
||||
return getStringFormatProperty(KEY_BIT_RATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all tags.
|
||||
*
|
||||
* @return tags as a JSONObject
|
||||
*/
|
||||
public JSONObject getTags() {
|
||||
return getFormatProperty(KEY_TAGS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all streams.
|
||||
*
|
||||
* @return list of streams
|
||||
*/
|
||||
public List<StreamInformation> getStreams() {
|
||||
return streams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all chapters.
|
||||
*
|
||||
* @return list of chapters
|
||||
*/
|
||||
public List<Chapter> getChapters() {
|
||||
return chapters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property associated with the key.
|
||||
*
|
||||
* @param key property key
|
||||
* @return property as string or null if the key is not found
|
||||
*/
|
||||
public String getStringProperty(final String key) {
|
||||
JSONObject allProperties = getAllProperties();
|
||||
if (allProperties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (allProperties.has(key)) {
|
||||
return allProperties.optString(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property associated with the key.
|
||||
*
|
||||
* @param key property key
|
||||
* @return property as Long or null if the key is not found
|
||||
*/
|
||||
public Long getNumberProperty(String key) {
|
||||
JSONObject allProperties = getAllProperties();
|
||||
if (allProperties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (allProperties.has(key)) {
|
||||
return allProperties.optLong(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property associated with the key.
|
||||
*
|
||||
* @param key property key
|
||||
* @return property as a JSONObject or null if the key is not found
|
||||
*/
|
||||
public JSONObject getProperty(String key) {
|
||||
JSONObject allProperties = getAllProperties();
|
||||
if (allProperties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return allProperties.optJSONObject(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the format property associated with the key.
|
||||
*
|
||||
* @param key property key
|
||||
* @return format property as string or null if the key is not found
|
||||
*/
|
||||
public String getStringFormatProperty(final String key) {
|
||||
JSONObject formatProperties = getFormatProperties();
|
||||
if (formatProperties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (formatProperties.has(key)) {
|
||||
return formatProperties.optString(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the format property associated with the key.
|
||||
*
|
||||
* @param key property key
|
||||
* @return format property as Long or null if the key is not found
|
||||
*/
|
||||
public Long getNumberFormatProperty(String key) {
|
||||
JSONObject formatProperties = getFormatProperties();
|
||||
if (formatProperties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (formatProperties.has(key)) {
|
||||
return formatProperties.optLong(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the format property associated with the key.
|
||||
*
|
||||
* @param key property key
|
||||
* @return format property as a JSONObject or null if the key is not found
|
||||
*/
|
||||
public JSONObject getFormatProperty(String key) {
|
||||
JSONObject formatProperties = getFormatProperties();
|
||||
if (formatProperties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatProperties.optJSONObject(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all format properties defined.
|
||||
*
|
||||
* @return all format properties as a JSONObject or null if no format properties are defined
|
||||
*/
|
||||
public JSONObject getFormatProperties() {
|
||||
return jsonObject.optJSONObject(KEY_FORMAT_PROPERTIES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all properties defined.
|
||||
*
|
||||
* @return all properties as a JSONObject or null if no properties are defined
|
||||
*/
|
||||
public JSONObject getAllProperties() {
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.arthenica.smartexception.java.Exceptions;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A parser that constructs {@link MediaInformation} from FFprobe's json output.
|
||||
*/
|
||||
public class MediaInformationJsonParser {
|
||||
|
||||
public static final String KEY_STREAMS = "streams";
|
||||
public static final String KEY_CHAPTERS = "chapters";
|
||||
|
||||
/**
|
||||
* Extracts <code>MediaInformation</code> from the given FFprobe json output. Note that this
|
||||
* method does not throw {@link JSONException} as {@link #fromWithError(String)} does and
|
||||
* handles errors internally.
|
||||
*
|
||||
* @param ffprobeJsonOutput FFprobe json output
|
||||
* @return created {@link MediaInformation} instance of null if a parsing error occurs
|
||||
*/
|
||||
public static MediaInformation from(final String ffprobeJsonOutput) {
|
||||
try {
|
||||
return fromWithError(ffprobeJsonOutput);
|
||||
} catch (JSONException e) {
|
||||
Log.e(FFmpegKitConfig.TAG, String.format("MediaInformation parsing failed.%s", Exceptions.getStackTraceString(e)));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts MediaInformation from the given FFprobe json output.
|
||||
*
|
||||
* @param ffprobeJsonOutput ffprobe json output
|
||||
* @return created {@link MediaInformation} instance
|
||||
* @throws JSONException if a parsing error occurs
|
||||
*/
|
||||
public static MediaInformation fromWithError(final String ffprobeJsonOutput) throws JSONException {
|
||||
final JSONObject jsonObject = new JSONObject(ffprobeJsonOutput);
|
||||
final JSONArray streamArray = jsonObject.optJSONArray(KEY_STREAMS);
|
||||
final JSONArray chapterArray = jsonObject.optJSONArray(KEY_CHAPTERS);
|
||||
|
||||
ArrayList<StreamInformation> streamList = new ArrayList<>();
|
||||
for (int i = 0; streamArray != null && i < streamArray.length(); i++) {
|
||||
JSONObject streamObject = streamArray.optJSONObject(i);
|
||||
if (streamObject != null) {
|
||||
streamList.add(new StreamInformation(streamObject));
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<Chapter> chapterList = new ArrayList<>();
|
||||
for (int i = 0; chapterArray != null && i < chapterArray.length(); i++) {
|
||||
JSONObject chapterObject = chapterArray.optJSONObject(i);
|
||||
if (chapterObject != null) {
|
||||
chapterList.add(new Chapter(chapterObject));
|
||||
}
|
||||
}
|
||||
|
||||
return new MediaInformation(jsonObject, streamList, chapterList);
|
||||
}
|
||||
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (c) 2021 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
/**
|
||||
* <p>A custom FFprobe session, which produces a <code>MediaInformation</code> object using the
|
||||
* FFprobe output.
|
||||
*/
|
||||
public class MediaInformationSession extends AbstractSession implements Session {
|
||||
|
||||
/**
|
||||
* Media information extracted in the session.
|
||||
*/
|
||||
private MediaInformation mediaInformation;
|
||||
|
||||
/**
|
||||
* Session specific complete callback.
|
||||
*/
|
||||
private final MediaInformationSessionCompleteCallback completeCallback;
|
||||
|
||||
/**
|
||||
* Creates a new media information session.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @return created session
|
||||
*/
|
||||
public static MediaInformationSession create(final String[] arguments) {
|
||||
return new MediaInformationSession(arguments, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new media information session.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @param completeCallback session specific complete callback
|
||||
* @return created session
|
||||
*/
|
||||
public static MediaInformationSession create(final String[] arguments, final MediaInformationSessionCompleteCallback completeCallback) {
|
||||
return new MediaInformationSession(arguments, completeCallback, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new media information session.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @param completeCallback session specific complete callback
|
||||
* @param logCallback session specific log callback
|
||||
* @return created session
|
||||
*/
|
||||
public static MediaInformationSession create(final String[] arguments, final MediaInformationSessionCompleteCallback completeCallback, final LogCallback logCallback) {
|
||||
return new MediaInformationSession(arguments, completeCallback, logCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new media information session.
|
||||
*
|
||||
* @param arguments command arguments
|
||||
* @param completeCallback session specific complete callback
|
||||
* @param logCallback session specific log callback
|
||||
*/
|
||||
private MediaInformationSession(final String[] arguments, final MediaInformationSessionCompleteCallback completeCallback, final LogCallback logCallback) {
|
||||
super(arguments, logCallback, LogRedirectionStrategy.NEVER_PRINT_LOGS);
|
||||
|
||||
this.completeCallback = completeCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the media information extracted in this session.
|
||||
*
|
||||
* @return media information extracted or null if the command failed or the output can not be
|
||||
* parsed
|
||||
*/
|
||||
public MediaInformation getMediaInformation() {
|
||||
return mediaInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media information extracted in this session.
|
||||
*
|
||||
* @param mediaInformation media information extracted
|
||||
*/
|
||||
public void setMediaInformation(final MediaInformation mediaInformation) {
|
||||
this.mediaInformation = mediaInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the session specific complete callback.
|
||||
*
|
||||
* @return session specific complete callback
|
||||
*/
|
||||
public MediaInformationSessionCompleteCallback getCompleteCallback() {
|
||||
return completeCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFFmpeg() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFFprobe() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMediaInformation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
stringBuilder.append("MediaInformationSession{");
|
||||
stringBuilder.append("sessionId=");
|
||||
stringBuilder.append(sessionId);
|
||||
stringBuilder.append(", createTime=");
|
||||
stringBuilder.append(createTime);
|
||||
stringBuilder.append(", startTime=");
|
||||
stringBuilder.append(startTime);
|
||||
stringBuilder.append(", endTime=");
|
||||
stringBuilder.append(endTime);
|
||||
stringBuilder.append(", arguments=");
|
||||
stringBuilder.append(FFmpegKitConfig.argumentsToString(arguments));
|
||||
stringBuilder.append(", logs=");
|
||||
stringBuilder.append(getLogsAsString());
|
||||
stringBuilder.append(", state=");
|
||||
stringBuilder.append(state);
|
||||
stringBuilder.append(", returnCode=");
|
||||
stringBuilder.append(returnCode);
|
||||
stringBuilder.append(", failStackTrace=");
|
||||
stringBuilder.append('\'');
|
||||
stringBuilder.append(failStackTrace);
|
||||
stringBuilder.append('\'');
|
||||
stringBuilder.append('}');
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
/**
|
||||
* <p>Callback function that is invoked when an asynchronous <code>MediaInformation</code> session
|
||||
* has ended.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MediaInformationSessionCompleteCallback {
|
||||
|
||||
/**
|
||||
* <p>Called when a media information session has ended.
|
||||
*
|
||||
* @param session media information session
|
||||
*/
|
||||
void apply(final MediaInformationSession session);
|
||||
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright (c) 2021 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import com.arthenica.smartexception.java.Exceptions;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* <p>Responsible of loading native libraries.
|
||||
*/
|
||||
public class NativeLoader {
|
||||
|
||||
static final String[] FFMPEG_LIBRARIES = {"avutil", "swscale", "swresample", "avcodec", "avformat", "avfilter", "avdevice"};
|
||||
|
||||
static final String[] LIBRARIES_LINKED_WITH_CXX = {"chromaprint", "openh264", "rubberband", "snappy", "srt", "tesseract", "x265", "zimg", "libilbc"};
|
||||
|
||||
static boolean isTestModeDisabled() {
|
||||
return (System.getProperty("enable.ffmpeg.kit.test.mode") == null);
|
||||
}
|
||||
|
||||
private static void loadLibrary(final String libraryName) {
|
||||
if (isTestModeDisabled()) {
|
||||
try {
|
||||
System.loadLibrary(libraryName);
|
||||
} catch (final UnsatisfiedLinkError e) {
|
||||
throw new Error(String.format("FFmpegKit failed to start on %s.", getDeviceDebugInformation()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> loadExternalLibraries() {
|
||||
if (isTestModeDisabled()) {
|
||||
return Packages.getExternalLibraries();
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private static String loadNativeAbi() {
|
||||
if (isTestModeDisabled()) {
|
||||
return AbiDetect.getNativeAbi();
|
||||
} else {
|
||||
return Abi.ABI_X86_64.getName();
|
||||
}
|
||||
}
|
||||
|
||||
static String loadAbi() {
|
||||
if (isTestModeDisabled()) {
|
||||
return AbiDetect.getAbi();
|
||||
} else {
|
||||
return Abi.ABI_X86_64.getName();
|
||||
}
|
||||
}
|
||||
|
||||
static String loadPackageName() {
|
||||
if (isTestModeDisabled()) {
|
||||
return Packages.getPackageName();
|
||||
} else {
|
||||
return "test";
|
||||
}
|
||||
}
|
||||
|
||||
static String loadVersion() {
|
||||
final String version = "6.0";
|
||||
|
||||
if (isTestModeDisabled()) {
|
||||
return FFmpegKitConfig.getVersion();
|
||||
} else if (loadIsLTSBuild()) {
|
||||
return String.format("%s-lts", version);
|
||||
} else {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
||||
static boolean loadIsLTSBuild() {
|
||||
if (isTestModeDisabled()) {
|
||||
return AbiDetect.isNativeLTSBuild();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static int loadLogLevel() {
|
||||
if (isTestModeDisabled()) {
|
||||
return FFmpegKitConfig.getNativeLogLevel();
|
||||
} else {
|
||||
return Level.AV_LOG_DEBUG.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
static String loadBuildDate() {
|
||||
if (isTestModeDisabled()) {
|
||||
return FFmpegKitConfig.getBuildDate();
|
||||
} else {
|
||||
return new SimpleDateFormat("yyyyMMdd", Locale.getDefault()).format(new Date());
|
||||
}
|
||||
}
|
||||
|
||||
static void enableRedirection() {
|
||||
if (isTestModeDisabled()) {
|
||||
FFmpegKitConfig.enableRedirection();
|
||||
}
|
||||
}
|
||||
|
||||
static void loadFFmpegKitAbiDetect() {
|
||||
loadLibrary("ffmpegkit_abidetect");
|
||||
}
|
||||
|
||||
static boolean loadFFmpeg() {
|
||||
boolean nativeFFmpegLoaded = false;
|
||||
boolean nativeFFmpegTriedAndFailed = false;
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
|
||||
/* LOADING LINKED LIBRARIES MANUALLY ON API < 21 */
|
||||
final List<String> externalLibrariesEnabled = loadExternalLibraries();
|
||||
for (String dependantLibrary : LIBRARIES_LINKED_WITH_CXX) {
|
||||
if (externalLibrariesEnabled.contains(dependantLibrary)) {
|
||||
loadLibrary("c++_shared");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (AbiDetect.ARM_V7A.equals(loadNativeAbi())) {
|
||||
try {
|
||||
for (String ffmpegLibrary : FFMPEG_LIBRARIES) {
|
||||
loadLibrary(ffmpegLibrary + "_neon");
|
||||
}
|
||||
nativeFFmpegLoaded = true;
|
||||
} catch (final Error e) {
|
||||
android.util.Log.i(FFmpegKitConfig.TAG, String.format("NEON supported armeabi-v7a ffmpeg library not found. Loading default armeabi-v7a library.%s", Exceptions.getStackTraceString(e)));
|
||||
nativeFFmpegTriedAndFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nativeFFmpegLoaded) {
|
||||
for (String ffmpegLibrary : FFMPEG_LIBRARIES) {
|
||||
loadLibrary(ffmpegLibrary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nativeFFmpegTriedAndFailed;
|
||||
}
|
||||
|
||||
static void loadFFmpegKit(final boolean nativeFFmpegTriedAndFailed) {
|
||||
boolean nativeFFmpegKitLoaded = false;
|
||||
|
||||
if (!nativeFFmpegTriedAndFailed && AbiDetect.ARM_V7A.equals(loadNativeAbi())) {
|
||||
try {
|
||||
|
||||
/*
|
||||
* THE TRY TO LOAD ARM-V7A-NEON FIRST. IF NOT LOAD DEFAULT ARM-V7A
|
||||
*/
|
||||
|
||||
loadLibrary("ffmpegkit_armv7a_neon");
|
||||
nativeFFmpegKitLoaded = true;
|
||||
AbiDetect.setArmV7aNeonLoaded();
|
||||
} catch (final Error e) {
|
||||
android.util.Log.i(FFmpegKitConfig.TAG, String.format("NEON supported armeabi-v7a ffmpegkit library not found. Loading default armeabi-v7a library.%s", Exceptions.getStackTraceString(e)));
|
||||
}
|
||||
}
|
||||
|
||||
if (!nativeFFmpegKitLoaded) {
|
||||
loadLibrary("ffmpegkit");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
static String getDeviceDebugInformation() {
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
stringBuilder.append("brand: ");
|
||||
stringBuilder.append(Build.BRAND);
|
||||
stringBuilder.append(", model: ");
|
||||
stringBuilder.append(Build.MODEL);
|
||||
stringBuilder.append(", device: ");
|
||||
stringBuilder.append(Build.DEVICE);
|
||||
stringBuilder.append(", api level: ");
|
||||
stringBuilder.append(Build.VERSION.SDK_INT);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
stringBuilder.append(", abis: ");
|
||||
stringBuilder.append(FFmpegKitConfig.argumentsToString(Build.SUPPORTED_ABIS));
|
||||
stringBuilder.append(", 32bit abis: ");
|
||||
stringBuilder.append(FFmpegKitConfig.argumentsToString(Build.SUPPORTED_32_BIT_ABIS));
|
||||
stringBuilder.append(", 64bit abis: ");
|
||||
stringBuilder.append(FFmpegKitConfig.argumentsToString(Build.SUPPORTED_64_BIT_ABIS));
|
||||
} else {
|
||||
stringBuilder.append(", cpu abis: ");
|
||||
stringBuilder.append(Build.CPU_ABI);
|
||||
stringBuilder.append(", cpu abi2s: ");
|
||||
stringBuilder.append(Build.CPU_ABI2);
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>Helper class to extract binary package information.
|
||||
*/
|
||||
public class Packages {
|
||||
|
||||
private static final List<String> supportedExternalLibraries;
|
||||
|
||||
static {
|
||||
supportedExternalLibraries = new ArrayList<>();
|
||||
supportedExternalLibraries.add("dav1d");
|
||||
supportedExternalLibraries.add("fontconfig");
|
||||
supportedExternalLibraries.add("freetype");
|
||||
supportedExternalLibraries.add("fribidi");
|
||||
supportedExternalLibraries.add("gmp");
|
||||
supportedExternalLibraries.add("gnutls");
|
||||
supportedExternalLibraries.add("kvazaar");
|
||||
supportedExternalLibraries.add("mp3lame");
|
||||
supportedExternalLibraries.add("libass");
|
||||
supportedExternalLibraries.add("iconv");
|
||||
supportedExternalLibraries.add("libilbc");
|
||||
supportedExternalLibraries.add("libtheora");
|
||||
supportedExternalLibraries.add("libvidstab");
|
||||
supportedExternalLibraries.add("libvorbis");
|
||||
supportedExternalLibraries.add("libvpx");
|
||||
supportedExternalLibraries.add("libwebp");
|
||||
supportedExternalLibraries.add("libxml2");
|
||||
supportedExternalLibraries.add("opencore-amr");
|
||||
supportedExternalLibraries.add("openh264");
|
||||
supportedExternalLibraries.add("openssl");
|
||||
supportedExternalLibraries.add("opus");
|
||||
supportedExternalLibraries.add("rubberband");
|
||||
supportedExternalLibraries.add("sdl2");
|
||||
supportedExternalLibraries.add("shine");
|
||||
supportedExternalLibraries.add("snappy");
|
||||
supportedExternalLibraries.add("soxr");
|
||||
supportedExternalLibraries.add("speex");
|
||||
supportedExternalLibraries.add("srt");
|
||||
supportedExternalLibraries.add("tesseract");
|
||||
supportedExternalLibraries.add("twolame");
|
||||
supportedExternalLibraries.add("x264");
|
||||
supportedExternalLibraries.add("x265");
|
||||
supportedExternalLibraries.add("xvid");
|
||||
supportedExternalLibraries.add("zimg");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the FFmpegKit binary package name.
|
||||
*
|
||||
* @return predicted FFmpegKit binary package name
|
||||
*/
|
||||
public static String getPackageName() {
|
||||
final List<String> externalLibraryList = getExternalLibraries();
|
||||
final boolean speex = externalLibraryList.contains("speex");
|
||||
final boolean fribidi = externalLibraryList.contains("fribidi");
|
||||
final boolean gnutls = externalLibraryList.contains("gnutls");
|
||||
final boolean xvid = externalLibraryList.contains("xvid");
|
||||
|
||||
boolean minGpl = false;
|
||||
boolean https = false;
|
||||
boolean httpsGpl = false;
|
||||
boolean audio = false;
|
||||
boolean video = false;
|
||||
boolean full = false;
|
||||
boolean fullGpl = false;
|
||||
|
||||
if (speex && fribidi) {
|
||||
if (xvid) {
|
||||
fullGpl = true;
|
||||
} else {
|
||||
full = true;
|
||||
}
|
||||
} else if (speex) {
|
||||
audio = true;
|
||||
} else if (fribidi) {
|
||||
video = true;
|
||||
} else if (xvid) {
|
||||
if (gnutls) {
|
||||
httpsGpl = true;
|
||||
} else {
|
||||
minGpl = true;
|
||||
}
|
||||
} else {
|
||||
if (gnutls) {
|
||||
https = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (fullGpl) {
|
||||
if (externalLibraryList.contains("dav1d") &&
|
||||
externalLibraryList.contains("fontconfig") &&
|
||||
externalLibraryList.contains("freetype") &&
|
||||
externalLibraryList.contains("fribidi") &&
|
||||
externalLibraryList.contains("gmp") &&
|
||||
externalLibraryList.contains("gnutls") &&
|
||||
externalLibraryList.contains("kvazaar") &&
|
||||
externalLibraryList.contains("mp3lame") &&
|
||||
externalLibraryList.contains("libass") &&
|
||||
externalLibraryList.contains("iconv") &&
|
||||
externalLibraryList.contains("libilbc") &&
|
||||
externalLibraryList.contains("libtheora") &&
|
||||
externalLibraryList.contains("libvidstab") &&
|
||||
externalLibraryList.contains("libvorbis") &&
|
||||
externalLibraryList.contains("libvpx") &&
|
||||
externalLibraryList.contains("libwebp") &&
|
||||
externalLibraryList.contains("libxml2") &&
|
||||
externalLibraryList.contains("opencore-amr") &&
|
||||
externalLibraryList.contains("opus") &&
|
||||
externalLibraryList.contains("shine") &&
|
||||
externalLibraryList.contains("snappy") &&
|
||||
externalLibraryList.contains("soxr") &&
|
||||
externalLibraryList.contains("speex") &&
|
||||
externalLibraryList.contains("twolame") &&
|
||||
externalLibraryList.contains("x264") &&
|
||||
externalLibraryList.contains("x265") &&
|
||||
externalLibraryList.contains("xvid") &&
|
||||
externalLibraryList.contains("zimg")) {
|
||||
return "full-gpl";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
if (full) {
|
||||
if (externalLibraryList.contains("dav1d") &&
|
||||
externalLibraryList.contains("fontconfig") &&
|
||||
externalLibraryList.contains("freetype") &&
|
||||
externalLibraryList.contains("fribidi") &&
|
||||
externalLibraryList.contains("gmp") &&
|
||||
externalLibraryList.contains("gnutls") &&
|
||||
externalLibraryList.contains("kvazaar") &&
|
||||
externalLibraryList.contains("mp3lame") &&
|
||||
externalLibraryList.contains("libass") &&
|
||||
externalLibraryList.contains("iconv") &&
|
||||
externalLibraryList.contains("libilbc") &&
|
||||
externalLibraryList.contains("libtheora") &&
|
||||
externalLibraryList.contains("libvorbis") &&
|
||||
externalLibraryList.contains("libvpx") &&
|
||||
externalLibraryList.contains("libwebp") &&
|
||||
externalLibraryList.contains("libxml2") &&
|
||||
externalLibraryList.contains("opencore-amr") &&
|
||||
externalLibraryList.contains("opus") &&
|
||||
externalLibraryList.contains("shine") &&
|
||||
externalLibraryList.contains("snappy") &&
|
||||
externalLibraryList.contains("soxr") &&
|
||||
externalLibraryList.contains("speex") &&
|
||||
externalLibraryList.contains("twolame") &&
|
||||
externalLibraryList.contains("zimg")) {
|
||||
return "full";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
if (video) {
|
||||
if (externalLibraryList.contains("dav1d") &&
|
||||
externalLibraryList.contains("fontconfig") &&
|
||||
externalLibraryList.contains("freetype") &&
|
||||
externalLibraryList.contains("fribidi") &&
|
||||
externalLibraryList.contains("kvazaar") &&
|
||||
externalLibraryList.contains("libass") &&
|
||||
externalLibraryList.contains("iconv") &&
|
||||
externalLibraryList.contains("libtheora") &&
|
||||
externalLibraryList.contains("libvpx") &&
|
||||
externalLibraryList.contains("libwebp") &&
|
||||
externalLibraryList.contains("snappy") &&
|
||||
externalLibraryList.contains("zimg")) {
|
||||
return "video";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
if (audio) {
|
||||
if (externalLibraryList.contains("mp3lame") &&
|
||||
externalLibraryList.contains("libilbc") &&
|
||||
externalLibraryList.contains("libvorbis") &&
|
||||
externalLibraryList.contains("opencore-amr") &&
|
||||
externalLibraryList.contains("opus") &&
|
||||
externalLibraryList.contains("shine") &&
|
||||
externalLibraryList.contains("soxr") &&
|
||||
externalLibraryList.contains("speex") &&
|
||||
externalLibraryList.contains("twolame")) {
|
||||
return "audio";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
if (httpsGpl) {
|
||||
if (externalLibraryList.contains("gmp") &&
|
||||
externalLibraryList.contains("gnutls") &&
|
||||
externalLibraryList.contains("libvidstab") &&
|
||||
externalLibraryList.contains("x264") &&
|
||||
externalLibraryList.contains("x265") &&
|
||||
externalLibraryList.contains("xvid")) {
|
||||
return "https-gpl";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
if (https) {
|
||||
if (externalLibraryList.contains("gmp") &&
|
||||
externalLibraryList.contains("gnutls")) {
|
||||
return "https";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
if (minGpl) {
|
||||
if (externalLibraryList.contains("libvidstab") &&
|
||||
externalLibraryList.contains("x264") &&
|
||||
externalLibraryList.contains("x265") &&
|
||||
externalLibraryList.contains("xvid")) {
|
||||
return "min-gpl";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
if (externalLibraryList.size() == 0) {
|
||||
return "min";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns enabled external libraries by FFmpeg.
|
||||
*
|
||||
* @return enabled external libraries
|
||||
*/
|
||||
public static List<String> getExternalLibraries() {
|
||||
final String buildConfiguration = AbiDetect.getNativeBuildConf();
|
||||
|
||||
final List<String> enabledLibraryList = new ArrayList<>();
|
||||
for (String supportedExternalLibrary : supportedExternalLibraries) {
|
||||
if (buildConfiguration.contains("enable-" + supportedExternalLibrary) ||
|
||||
buildConfiguration.contains("enable-lib" + supportedExternalLibrary)) {
|
||||
enabledLibraryList.add(supportedExternalLibrary);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(enabledLibraryList);
|
||||
|
||||
return enabledLibraryList;
|
||||
}
|
||||
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2021 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
public class ReturnCode {
|
||||
|
||||
public static int SUCCESS = 0;
|
||||
|
||||
public static int CANCEL = 255;
|
||||
|
||||
private final int value;
|
||||
|
||||
public ReturnCode(final int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static boolean isSuccess(final ReturnCode returnCode) {
|
||||
return (returnCode != null && returnCode.getValue() == SUCCESS);
|
||||
}
|
||||
|
||||
public static boolean isCancel(final ReturnCode returnCode) {
|
||||
return (returnCode != null && returnCode.getValue() == CANCEL);
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean isValueSuccess() {
|
||||
return (value == SUCCESS);
|
||||
}
|
||||
|
||||
public boolean isValueError() {
|
||||
return ((value != SUCCESS) && (value != CANCEL));
|
||||
}
|
||||
|
||||
public boolean isValueCancel() {
|
||||
return (value == CANCEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
}
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright (c) 2021 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* <p>Common interface for all <code>FFmpegKit</code> sessions.
|
||||
*/
|
||||
public interface Session {
|
||||
|
||||
/**
|
||||
* Returns the session specific log callback.
|
||||
*
|
||||
* @return session specific log callback
|
||||
*/
|
||||
LogCallback getLogCallback();
|
||||
|
||||
/**
|
||||
* Returns the session identifier.
|
||||
*
|
||||
* @return session identifier
|
||||
*/
|
||||
long getSessionId();
|
||||
|
||||
/**
|
||||
* Returns session create time.
|
||||
*
|
||||
* @return session create time
|
||||
*/
|
||||
Date getCreateTime();
|
||||
|
||||
/**
|
||||
* Returns session start time.
|
||||
*
|
||||
* @return session start time
|
||||
*/
|
||||
Date getStartTime();
|
||||
|
||||
/**
|
||||
* Returns session end time.
|
||||
*
|
||||
* @return session end time
|
||||
*/
|
||||
Date getEndTime();
|
||||
|
||||
/**
|
||||
* Returns the time taken to execute this session.
|
||||
*
|
||||
* @return time taken to execute this session in milliseconds or zero (0) if the session is
|
||||
* not over yet
|
||||
*/
|
||||
long getDuration();
|
||||
|
||||
/**
|
||||
* Returns command arguments as an array.
|
||||
*
|
||||
* @return command arguments as an array
|
||||
*/
|
||||
String[] getArguments();
|
||||
|
||||
/**
|
||||
* Returns command arguments as a concatenated string.
|
||||
*
|
||||
* @return command arguments as a concatenated string
|
||||
*/
|
||||
String getCommand();
|
||||
|
||||
/**
|
||||
* Returns all log entries generated for this session. If there are asynchronous
|
||||
* messages that are not delivered yet, this method waits for them until the given timeout.
|
||||
*
|
||||
* @param waitTimeout wait timeout for asynchronous messages in milliseconds
|
||||
* @return list of log entries generated for this session
|
||||
*/
|
||||
List<Log> getAllLogs(final int waitTimeout);
|
||||
|
||||
/**
|
||||
* Returns all log entries generated for this session. If there are asynchronous
|
||||
* messages that are not delivered yet, this method waits for them.
|
||||
*
|
||||
* @return list of log entries generated for this session
|
||||
*/
|
||||
List<Log> getAllLogs();
|
||||
|
||||
/**
|
||||
* Returns all log entries delivered for this session. Note that if there are asynchronous log
|
||||
* messages that are not delivered yet, this method will not wait for them and will return
|
||||
* immediately.
|
||||
*
|
||||
* @return list of log entries received for this session
|
||||
*/
|
||||
List<Log> getLogs();
|
||||
|
||||
/**
|
||||
* Returns all log entries generated for this session as a concatenated string. If there are
|
||||
* asynchronous messages that are not delivered yet, this method waits for them until
|
||||
* the given timeout.
|
||||
*
|
||||
* @param waitTimeout wait timeout for asynchronous messages in milliseconds
|
||||
* @return all log entries generated for this session as a concatenated string
|
||||
*/
|
||||
String getAllLogsAsString(final int waitTimeout);
|
||||
|
||||
/**
|
||||
* Returns all log entries generated for this session as a concatenated string. If there are
|
||||
* asynchronous messages that are not delivered yet, this method waits for them.
|
||||
*
|
||||
* @return all log entries generated for this session as a concatenated string
|
||||
*/
|
||||
String getAllLogsAsString();
|
||||
|
||||
/**
|
||||
* Returns all log entries delivered for this session as a concatenated string. Note that if
|
||||
* there are asynchronous log messages that are not delivered yet, this method will not wait
|
||||
* for them and will return immediately.
|
||||
*
|
||||
* @return list of log entries received for this session
|
||||
*/
|
||||
String getLogsAsString();
|
||||
|
||||
/**
|
||||
* Returns the log output generated while running the session.
|
||||
*
|
||||
* @return log output generated
|
||||
*/
|
||||
String getOutput();
|
||||
|
||||
/**
|
||||
* Returns the state of the session.
|
||||
*
|
||||
* @return state of the session
|
||||
*/
|
||||
SessionState getState();
|
||||
|
||||
/**
|
||||
* Returns the return code for this session. Note that return code is only set for sessions
|
||||
* that end with COMPLETED state. If a session is not started, still running or failed then
|
||||
* this method returns null.
|
||||
*
|
||||
* @return the return code for this session if the session is COMPLETED, null if session is
|
||||
* not started, still running or failed
|
||||
*/
|
||||
ReturnCode getReturnCode();
|
||||
|
||||
/**
|
||||
* Returns the stack trace of the exception received while executing this session.
|
||||
* <p>
|
||||
* The stack trace is only set for sessions that end with FAILED state. For sessions that has
|
||||
* COMPLETED state this method returns null.
|
||||
*
|
||||
* @return stack trace of the exception received while executing this session, null if session
|
||||
* is not started, still running or completed
|
||||
*/
|
||||
String getFailStackTrace();
|
||||
|
||||
/**
|
||||
* Returns session specific log redirection strategy.
|
||||
*
|
||||
* @return session specific log redirection strategy
|
||||
*/
|
||||
LogRedirectionStrategy getLogRedirectionStrategy();
|
||||
|
||||
/**
|
||||
* Returns whether there are still asynchronous messages being transmitted for this
|
||||
* session or not.
|
||||
*
|
||||
* @return true if there are still asynchronous messages being transmitted, false
|
||||
* otherwise
|
||||
*/
|
||||
boolean thereAreAsynchronousMessagesInTransmit();
|
||||
|
||||
/**
|
||||
* Adds a new log entry for this session.
|
||||
* <p>
|
||||
* It is invoked internally by <code>FFmpegKit</code> library methods. Must not be used by user
|
||||
* applications.
|
||||
*
|
||||
* @param log log entry
|
||||
*/
|
||||
void addLog(final Log log);
|
||||
|
||||
/**
|
||||
* Returns the future created for this session, if it is executed asynchronously.
|
||||
*
|
||||
* @return future that runs this session asynchronously
|
||||
*/
|
||||
Future<?> getFuture();
|
||||
|
||||
/**
|
||||
* Returns whether it is an <code>FFmpeg</code> session or not.
|
||||
*
|
||||
* @return true if it is an <code>FFmpeg</code> session, false otherwise
|
||||
*/
|
||||
boolean isFFmpeg();
|
||||
|
||||
/**
|
||||
* Returns whether it is an <code>FFprobe</code> session or not.
|
||||
*
|
||||
* @return true if it is an <code>FFprobe</code> session, false otherwise
|
||||
*/
|
||||
boolean isFFprobe();
|
||||
|
||||
/**
|
||||
* Returns whether it is a <code>MediaInformation</code> session or not.
|
||||
*
|
||||
* @return true if it is a <code>MediaInformation</code> session, false otherwise
|
||||
*/
|
||||
boolean isMediaInformation();
|
||||
|
||||
/**
|
||||
* Cancels running the session.
|
||||
*/
|
||||
void cancel();
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2021 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
public enum SessionState {
|
||||
CREATED,
|
||||
RUNNING,
|
||||
FAILED,
|
||||
COMPLETED
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2021 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
/**
|
||||
* <p>Lists signals handled by FFmpegKit library.
|
||||
*/
|
||||
public enum Signal {
|
||||
|
||||
SIGINT(2),
|
||||
SIGQUIT(3),
|
||||
SIGPIPE(13),
|
||||
SIGTERM(15),
|
||||
SIGXCPU(24);
|
||||
|
||||
private final int value;
|
||||
|
||||
Signal(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
/**
|
||||
* <p>Statistics entry for an FFmpeg execute session.
|
||||
*/
|
||||
public class Statistics {
|
||||
private long sessionId;
|
||||
private int videoFrameNumber;
|
||||
private float videoFps;
|
||||
private float videoQuality;
|
||||
private long size;
|
||||
private double time;
|
||||
private double bitrate;
|
||||
private double speed;
|
||||
|
||||
public Statistics(final long sessionId, final int videoFrameNumber, final float videoFps, final float videoQuality, final long size, final double time, final double bitrate, final double speed) {
|
||||
this.sessionId = sessionId;
|
||||
this.videoFrameNumber = videoFrameNumber;
|
||||
this.videoFps = videoFps;
|
||||
this.videoQuality = videoQuality;
|
||||
this.size = size;
|
||||
this.time = time;
|
||||
this.bitrate = bitrate;
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
public long getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(long sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public int getVideoFrameNumber() {
|
||||
return videoFrameNumber;
|
||||
}
|
||||
|
||||
public void setVideoFrameNumber(int videoFrameNumber) {
|
||||
this.videoFrameNumber = videoFrameNumber;
|
||||
}
|
||||
|
||||
public float getVideoFps() {
|
||||
return videoFps;
|
||||
}
|
||||
|
||||
public void setVideoFps(float videoFps) {
|
||||
this.videoFps = videoFps;
|
||||
}
|
||||
|
||||
public float getVideoQuality() {
|
||||
return videoQuality;
|
||||
}
|
||||
|
||||
public void setVideoQuality(float videoQuality) {
|
||||
this.videoQuality = videoQuality;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(long size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public double getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(double time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public double getBitrate() {
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
public void setBitrate(double bitrate) {
|
||||
this.bitrate = bitrate;
|
||||
}
|
||||
|
||||
public double getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
public void setSpeed(double speed) {
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
stringBuilder.append("Statistics{");
|
||||
stringBuilder.append("sessionId=");
|
||||
stringBuilder.append(sessionId);
|
||||
stringBuilder.append(", videoFrameNumber=");
|
||||
stringBuilder.append(videoFrameNumber);
|
||||
stringBuilder.append(", videoFps=");
|
||||
stringBuilder.append(videoFps);
|
||||
stringBuilder.append(", videoQuality=");
|
||||
stringBuilder.append(videoQuality);
|
||||
stringBuilder.append(", size=");
|
||||
stringBuilder.append(size);
|
||||
stringBuilder.append(", time=");
|
||||
stringBuilder.append(time);
|
||||
stringBuilder.append(", bitrate=");
|
||||
stringBuilder.append(bitrate);
|
||||
stringBuilder.append(", speed=");
|
||||
stringBuilder.append(speed);
|
||||
stringBuilder.append('}');
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
/**
|
||||
* <p>Callback function that receives statistics generated for <code>FFmpegKit</code> sessions.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface StatisticsCallback {
|
||||
|
||||
/**
|
||||
* <p>Called when a statistics entry is received.
|
||||
*
|
||||
* @param statistics statistics entry
|
||||
*/
|
||||
void apply(final Statistics statistics);
|
||||
|
||||
}
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2022 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Stream information class.
|
||||
*/
|
||||
public class StreamInformation {
|
||||
|
||||
/* COMMON KEYS */
|
||||
public static final String KEY_INDEX = "index";
|
||||
public static final String KEY_TYPE = "codec_type";
|
||||
public static final String KEY_CODEC = "codec_name";
|
||||
public static final String KEY_CODEC_LONG = "codec_long_name";
|
||||
public static final String KEY_FORMAT = "pix_fmt";
|
||||
public static final String KEY_WIDTH = "width";
|
||||
public static final String KEY_HEIGHT = "height";
|
||||
public static final String KEY_BIT_RATE = "bit_rate";
|
||||
public static final String KEY_SAMPLE_RATE = "sample_rate";
|
||||
public static final String KEY_SAMPLE_FORMAT = "sample_fmt";
|
||||
public static final String KEY_CHANNEL_LAYOUT = "channel_layout";
|
||||
public static final String KEY_SAMPLE_ASPECT_RATIO = "sample_aspect_ratio";
|
||||
public static final String KEY_DISPLAY_ASPECT_RATIO = "display_aspect_ratio";
|
||||
public static final String KEY_AVERAGE_FRAME_RATE = "avg_frame_rate";
|
||||
public static final String KEY_REAL_FRAME_RATE = "r_frame_rate";
|
||||
public static final String KEY_TIME_BASE = "time_base";
|
||||
public static final String KEY_CODEC_TIME_BASE = "codec_time_base";
|
||||
public static final String KEY_TAGS = "tags";
|
||||
|
||||
/**
|
||||
* Stores all properties.
|
||||
*/
|
||||
private final JSONObject jsonObject;
|
||||
|
||||
public StreamInformation(final JSONObject jsonObject) {
|
||||
this.jsonObject = jsonObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stream index.
|
||||
*
|
||||
* @return stream index, starting from zero
|
||||
*/
|
||||
public Long getIndex() {
|
||||
return getNumberProperty(KEY_INDEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stream type.
|
||||
*
|
||||
* @return stream type; audio or video
|
||||
*/
|
||||
public String getType() {
|
||||
return getStringProperty(KEY_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stream codec.
|
||||
*
|
||||
* @return stream codec
|
||||
*/
|
||||
public String getCodec() {
|
||||
return getStringProperty(KEY_CODEC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stream codec in long format.
|
||||
*
|
||||
* @return stream codec with additional profile and mode information
|
||||
*/
|
||||
public String getCodecLong() {
|
||||
return getStringProperty(KEY_CODEC_LONG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stream format.
|
||||
*
|
||||
* @return stream format
|
||||
*/
|
||||
public String getFormat() {
|
||||
return getStringProperty(KEY_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns width.
|
||||
*
|
||||
* @return width in pixels
|
||||
*/
|
||||
public Long getWidth() {
|
||||
return getNumberProperty(KEY_WIDTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns height.
|
||||
*
|
||||
* @return height in pixels
|
||||
*/
|
||||
public Long getHeight() {
|
||||
return getNumberProperty(KEY_HEIGHT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bitrate.
|
||||
*
|
||||
* @return bitrate in kb/s
|
||||
*/
|
||||
public String getBitrate() {
|
||||
return getStringProperty(KEY_BIT_RATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sample rate.
|
||||
*
|
||||
* @return sample rate in hz
|
||||
*/
|
||||
public String getSampleRate() {
|
||||
return getStringProperty(KEY_SAMPLE_RATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sample format.
|
||||
*
|
||||
* @return sample format
|
||||
*/
|
||||
public String getSampleFormat() {
|
||||
return getStringProperty(KEY_SAMPLE_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns channel layout.
|
||||
*
|
||||
* @return channel layout
|
||||
*/
|
||||
public String getChannelLayout() {
|
||||
return getStringProperty(KEY_CHANNEL_LAYOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sample aspect ratio.
|
||||
*
|
||||
* @return sample aspect ratio
|
||||
*/
|
||||
public String getSampleAspectRatio() {
|
||||
return getStringProperty(KEY_SAMPLE_ASPECT_RATIO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns display aspect ratio.
|
||||
*
|
||||
* @return display aspect ratio
|
||||
*/
|
||||
public String getDisplayAspectRatio() {
|
||||
return getStringProperty(KEY_DISPLAY_ASPECT_RATIO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns display aspect ratio.
|
||||
*
|
||||
* @return average frame rate in fps
|
||||
*/
|
||||
public String getAverageFrameRate() {
|
||||
return getStringProperty(KEY_AVERAGE_FRAME_RATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns real frame rate.
|
||||
*
|
||||
* @return real frame rate in tbr
|
||||
*/
|
||||
public String getRealFrameRate() {
|
||||
return getStringProperty(KEY_REAL_FRAME_RATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns time base.
|
||||
*
|
||||
* @return time base in tbn
|
||||
*/
|
||||
public String getTimeBase() {
|
||||
return getStringProperty(KEY_TIME_BASE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns codec time base.
|
||||
*
|
||||
* @return codec time base in tbc
|
||||
*/
|
||||
public String getCodecTimeBase() {
|
||||
return getStringProperty(KEY_CODEC_TIME_BASE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all tags.
|
||||
*
|
||||
* @return tags object
|
||||
*/
|
||||
public JSONObject getTags() {
|
||||
return getProperty(KEY_TAGS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stream property associated with the key.
|
||||
*
|
||||
* @param key property key
|
||||
* @return stream property as string or null if the key is not found
|
||||
*/
|
||||
public String getStringProperty(final String key) {
|
||||
JSONObject allProperties = getAllProperties();
|
||||
if (allProperties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (allProperties.has(key)) {
|
||||
return allProperties.optString(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stream property associated with the key.
|
||||
*
|
||||
* @param key property key
|
||||
* @return stream property as Long or null if the key is not found
|
||||
*/
|
||||
public Long getNumberProperty(String key) {
|
||||
JSONObject allProperties = getAllProperties();
|
||||
if (allProperties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (allProperties.has(key)) {
|
||||
return allProperties.optLong(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stream property associated with the key.
|
||||
*
|
||||
* @param key property key
|
||||
* @return stream property as a JSONObject or null if the key is not found
|
||||
*/
|
||||
public JSONObject getProperty(String key) {
|
||||
JSONObject allProperties = getAllProperties();
|
||||
if (allProperties == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return allProperties.optJSONObject(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all stream properties defined.
|
||||
*
|
||||
* @return all stream properties as a JSONObject or null if no properties are defined
|
||||
*/
|
||||
public JSONObject getAllProperties() {
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
*.txt
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import static com.arthenica.ffmpegkit.FFmpegSessionTest.TEST_ARGUMENTS;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>Tests for {@link FFmpegKitConfig} class.
|
||||
*/
|
||||
public class FFmpegKitConfigTest {
|
||||
|
||||
private static final String externalLibrariesCommandOutput = " configuration:\n" +
|
||||
" --cross-prefix=i686-linux-android-\n" +
|
||||
" --sysroot=/Users/taner/Library/Android/sdk/ndk-bundle/toolchains/ffmpeg-kit-i686/sysroot\n" +
|
||||
" --prefix=/Users/taner/Projects/ffmpeg-kit/prebuilt/android-x86/ffmpeg\n" +
|
||||
" --pkg-config=/usr/local/bin/pkg-config --extra-cflags='-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32 -Wno-unused-function -fstrict-aliasing -fPIC -DANDROID -D__ANDROID__ -D__ANDROID_API__=21 -O2 -I/Users/taner/Library/Android/sdk/ndk-bundle/toolchains/ffmpeg-kit-i686/sysroot/usr/include -I/Users/taner/Library/Android/sdk/ndk-bundle/toolchains/ffmpeg-kit-i686/sysroot/usr/local/include'\n" +
|
||||
" --extra-cxxflags='-std=c++11 -fno-exceptions -fno-rtti'\n" +
|
||||
" --extra-ldflags='-march=i686 -Wl,--gc-sections,--icf=safe -lc -lm -ldl -llog -lc++_shared -L/Users/taner/Library/Android/sdk/ndk-bundle/toolchains/ffmpeg-kit-i686/i686-linux-android/lib -L/Users/taner/Library/Android/sdk/ndk-bundle/toolchains/ffmpeg-kit-i686/sysroot/usr/lib -L/Users/taner/Library/Android/sdk/ndk-bundle/toolchains/ffmpeg-kit-i686/lib -L/Users/taner/Library/Android/sdk/ndk-bundle/platforms/android-21/arch-x86/usr/lib'\n" +
|
||||
" --enable-version3\n" +
|
||||
" --arch=i686\n" +
|
||||
" --cpu=i686\n" +
|
||||
" --target-os=android\n" +
|
||||
" --disable-neon\n" +
|
||||
" --disable-asm\n" +
|
||||
" --disable-inline-asm\n" +
|
||||
" --enable-cross-compile\n" +
|
||||
" --enable-pic\n" +
|
||||
" --enable-jni\n" +
|
||||
" --enable-libvorbis\n" +
|
||||
" --enable-optimizations\n" +
|
||||
" --enable-swscale\n" +
|
||||
" --enable-shared\n" +
|
||||
" --enable-v4l2-m2m\n" +
|
||||
" --enable-small\n" +
|
||||
" --disable-openssl\n" +
|
||||
" --disable-xmm-clobber-test\n" +
|
||||
" --disable-debug\n" +
|
||||
" --disable-neon-clobber-test\n" +
|
||||
" --disable-programs\n" +
|
||||
" --disable-postproc\n" +
|
||||
" --disable-doc\n" +
|
||||
" --disable-htmlpages\n" +
|
||||
" --disable-manpages\n" +
|
||||
" --disable-podpages\n" +
|
||||
" --disable-txtpages\n" +
|
||||
" --disable-static\n" +
|
||||
" --disable-sndio\n" +
|
||||
" --disable-schannel\n" +
|
||||
" --disable-securetransport\n" +
|
||||
" --disable-xlib\n" +
|
||||
" --disable-cuda\n" +
|
||||
" --disable-cuvid\n" +
|
||||
" --disable-nvenc\n" +
|
||||
" --disable-vaapi\n" +
|
||||
" --disable-vdpau\n" +
|
||||
" --disable-videotoolbox\n" +
|
||||
" --disable-audiotoolbox\n" +
|
||||
" --disable-appkit\n" +
|
||||
" --disable-alsa\n" +
|
||||
" --disable-cuda\n" +
|
||||
" --disable-cuvid\n" +
|
||||
" --disable-nvenc\n" +
|
||||
" --disable-vaapi\n" +
|
||||
" --disable-vdpau\n" +
|
||||
" --disable-zlib\n";
|
||||
|
||||
@Test
|
||||
public void getExternalLibraries() {
|
||||
|
||||
final List<String> supportedExternalLibraries = new ArrayList<>();
|
||||
supportedExternalLibraries.add("chromaprint");
|
||||
supportedExternalLibraries.add("dav1d");
|
||||
supportedExternalLibraries.add("fontconfig");
|
||||
supportedExternalLibraries.add("freetype");
|
||||
supportedExternalLibraries.add("fribidi");
|
||||
supportedExternalLibraries.add("gmp");
|
||||
supportedExternalLibraries.add("gnutls");
|
||||
supportedExternalLibraries.add("kvazaar");
|
||||
supportedExternalLibraries.add("lame");
|
||||
supportedExternalLibraries.add("libaom");
|
||||
supportedExternalLibraries.add("libass");
|
||||
supportedExternalLibraries.add("libiconv");
|
||||
supportedExternalLibraries.add("libilbc");
|
||||
supportedExternalLibraries.add("libtheora");
|
||||
supportedExternalLibraries.add("libvidstab");
|
||||
supportedExternalLibraries.add("libvorbis");
|
||||
supportedExternalLibraries.add("libvpx");
|
||||
supportedExternalLibraries.add("libwebp");
|
||||
supportedExternalLibraries.add("libxml2");
|
||||
supportedExternalLibraries.add("opencore-amr");
|
||||
supportedExternalLibraries.add("opus");
|
||||
supportedExternalLibraries.add("shine");
|
||||
supportedExternalLibraries.add("sdl");
|
||||
supportedExternalLibraries.add("snappy");
|
||||
supportedExternalLibraries.add("soxr");
|
||||
supportedExternalLibraries.add("speex");
|
||||
supportedExternalLibraries.add("tesseract");
|
||||
supportedExternalLibraries.add("twolame");
|
||||
supportedExternalLibraries.add("x264");
|
||||
supportedExternalLibraries.add("x265");
|
||||
supportedExternalLibraries.add("xvidcore");
|
||||
supportedExternalLibraries.add("android-zlib");
|
||||
supportedExternalLibraries.add("android-media-codec");
|
||||
|
||||
|
||||
final List<String> enabledList = new ArrayList<>();
|
||||
for (String supportedExternalLibrary : supportedExternalLibraries) {
|
||||
if (externalLibrariesCommandOutput.contains("enable-" + supportedExternalLibrary) ||
|
||||
externalLibrariesCommandOutput.contains("enable-lib" + supportedExternalLibrary)) {
|
||||
enabledList.add(supportedExternalLibrary);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(enabledList);
|
||||
|
||||
Assert.assertNotNull(enabledList);
|
||||
Assert.assertEquals(1, enabledList.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPackageName() {
|
||||
Assert.assertEquals("min", listToPackageName(Collections.singletonList("")));
|
||||
Assert.assertEquals("min-gpl", listToPackageName(Collections.singletonList("xvidcore")));
|
||||
Assert.assertEquals("full-gpl", listToPackageName(Arrays.asList("gnutls", "speex", "fribidi", "xvidcore")));
|
||||
Assert.assertEquals("full", listToPackageName(Arrays.asList("fribidi", "speex")));
|
||||
Assert.assertEquals("video", listToPackageName(Collections.singletonList("fribidi")));
|
||||
Assert.assertEquals("audio", listToPackageName(Collections.singletonList("speex")));
|
||||
Assert.assertEquals("https", listToPackageName(Collections.singletonList("gnutls")));
|
||||
Assert.assertEquals("https-gpl", listToPackageName(Arrays.asList("gnutls", "xvidcore")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractExtensionFromSafDisplayName() {
|
||||
String extension = FFmpegKitConfig.extractExtensionFromSafDisplayName("video.mp4 (2)");
|
||||
Assert.assertEquals("mp4", extension);
|
||||
|
||||
extension = FFmpegKitConfig.extractExtensionFromSafDisplayName("video file name.mp3 (2)");
|
||||
Assert.assertEquals("mp3", extension);
|
||||
|
||||
extension = FFmpegKitConfig.extractExtensionFromSafDisplayName("file.mp4");
|
||||
Assert.assertEquals("mp4", extension);
|
||||
|
||||
extension = FFmpegKitConfig.extractExtensionFromSafDisplayName("file name.mp4");
|
||||
Assert.assertEquals("mp4", extension);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSessionHistorySize() {
|
||||
int newSize = 15;
|
||||
FFmpegKitConfig.setSessionHistorySize(newSize);
|
||||
|
||||
for (int i = 1; i <= (newSize + 5); i++) {
|
||||
FFmpegSession.create(TEST_ARGUMENTS);
|
||||
Assert.assertTrue(FFmpegKitConfig.getSessions().size() <= newSize);
|
||||
}
|
||||
|
||||
newSize = 3;
|
||||
FFmpegKitConfig.setSessionHistorySize(newSize);
|
||||
for (int i = 1; i <= (newSize + 5); i++) {
|
||||
FFmpegSession.create(TEST_ARGUMENTS);
|
||||
Assert.assertTrue(FFmpegKitConfig.getSessions().size() <= newSize);
|
||||
}
|
||||
}
|
||||
|
||||
private String listToPackageName(final List<String> externalLibraryList) {
|
||||
boolean speex = externalLibraryList.contains("speex");
|
||||
boolean fribidi = externalLibraryList.contains("fribidi");
|
||||
boolean gnutls = externalLibraryList.contains("gnutls");
|
||||
boolean xvidcore = externalLibraryList.contains("xvidcore");
|
||||
|
||||
if (speex && fribidi) {
|
||||
if (xvidcore) {
|
||||
return "full-gpl";
|
||||
} else {
|
||||
return "full";
|
||||
}
|
||||
} else if (speex) {
|
||||
return "audio";
|
||||
} else if (fribidi) {
|
||||
return "video";
|
||||
} else if (xvidcore) {
|
||||
if (gnutls) {
|
||||
return "https-gpl";
|
||||
} else {
|
||||
return "min-gpl";
|
||||
}
|
||||
} else {
|
||||
if (gnutls) {
|
||||
return "https";
|
||||
} else {
|
||||
return "min";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+844
@@ -0,0 +1,844 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2020 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* <p>Tests for {@link FFmpegKit} class.
|
||||
*/
|
||||
public class FFmpegKitTest {
|
||||
|
||||
private static final String MEDIA_INFORMATION_MP3 =
|
||||
"{\n" +
|
||||
" \"streams\": [\n" +
|
||||
" {\n" +
|
||||
" \"index\": 0,\n" +
|
||||
" \"codec_name\": \"mp3\",\n" +
|
||||
" \"codec_long_name\": \"MP3 (MPEG audio layer 3)\",\n" +
|
||||
" \"codec_type\": \"audio\",\n" +
|
||||
" \"codec_time_base\": \"1/44100\",\n" +
|
||||
" \"codec_tag_string\": \"[0][0][0][0]\",\n" +
|
||||
" \"codec_tag\": \"0x0000\",\n" +
|
||||
" \"sample_fmt\": \"fltp\",\n" +
|
||||
" \"sample_rate\": \"44100\",\n" +
|
||||
" \"channels\": 2,\n" +
|
||||
" \"channel_layout\": \"stereo\",\n" +
|
||||
" \"bits_per_sample\": 0,\n" +
|
||||
" \"r_frame_rate\": \"0/0\",\n" +
|
||||
" \"avg_frame_rate\": \"0/0\",\n" +
|
||||
" \"time_base\": \"1/14112000\",\n" +
|
||||
" \"start_pts\": 169280,\n" +
|
||||
" \"start_time\": \"0.011995\",\n" +
|
||||
" \"duration_ts\": 4622376960,\n" +
|
||||
" \"duration\": \"327.549388\",\n" +
|
||||
" \"bit_rate\": \"320000\",\n" +
|
||||
" \"disposition\": {\n" +
|
||||
" \"default\": 0,\n" +
|
||||
" \"dub\": 0,\n" +
|
||||
" \"original\": 0,\n" +
|
||||
" \"comment\": 0,\n" +
|
||||
" \"lyrics\": 0,\n" +
|
||||
" \"karaoke\": 0,\n" +
|
||||
" \"forced\": 0,\n" +
|
||||
" \"hearing_impaired\": 0,\n" +
|
||||
" \"visual_impaired\": 0,\n" +
|
||||
" \"clean_effects\": 0,\n" +
|
||||
" \"attached_pic\": 0,\n" +
|
||||
" \"timed_thumbnails\": 0\n" +
|
||||
" },\n" +
|
||||
" \"tags\": {\n" +
|
||||
" \"encoder\": \"Lavf\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" ],\n" +
|
||||
" \"chapters\": [\n" +
|
||||
" {\n" +
|
||||
" \"id\": 0,\n" +
|
||||
" \"time_base\": \"1/22050\",\n" +
|
||||
" \"start\": 0,\n" +
|
||||
" \"start_time\": \"0.000000\",\n" +
|
||||
" \"end\": 11158238,\n" +
|
||||
" \"end_time\": \"506.042540\",\n" +
|
||||
" \"tags\": {\n" +
|
||||
" \"title\": \"1 Laying Plans - 2 Waging War\"\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"id\": 1,\n" +
|
||||
" \"time_base\": \"1/22050\",\n" +
|
||||
" \"start\": 11158238,\n" +
|
||||
" \"start_time\": \"506.042540\",\n" +
|
||||
" \"end\": 21433051,\n" +
|
||||
" \"end_time\": \"972.020454\",\n" +
|
||||
" \"tags\": {\n" +
|
||||
" \"title\": \"3 Attack By Stratagem - 4 Tactical Dispositions\"\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"id\": 2,\n" +
|
||||
" \"time_base\": \"1/22050\",\n" +
|
||||
" \"start\": 21433051,\n" +
|
||||
" \"start_time\": \"972.020454\",\n" +
|
||||
" \"end\": 35478685,\n" +
|
||||
" \"end_time\": \"1609.010658\",\n" +
|
||||
" \"tags\": {\n" +
|
||||
" \"title\": \"5 Energy - 6 Weak Points and Strong\"\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"id\": 3,\n" +
|
||||
" \"time_base\": \"1/22050\",\n" +
|
||||
" \"start\": 35478685,\n" +
|
||||
" \"start_time\": \"1609.010658\",\n" +
|
||||
" \"end\": 47187043,\n" +
|
||||
" \"end_time\": \"2140.001950\",\n" +
|
||||
" \"tags\": {\n" +
|
||||
" \"title\": \"7 Maneuvering - 8 Variation in Tactics\"\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"id\": 4,\n" +
|
||||
" \"time_base\": \"1/22050\",\n" +
|
||||
" \"start\": 47187043,\n" +
|
||||
" \"start_time\": \"2140.001950\",\n" +
|
||||
" \"end\": 66635594,\n" +
|
||||
" \"end_time\": \"3022.022404\",\n" +
|
||||
" \"tags\": {\n" +
|
||||
" \"title\": \"9 The Army on the March - 10 Terrain\"\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"id\": 5,\n" +
|
||||
" \"time_base\": \"1/22050\",\n" +
|
||||
" \"start\": 66635594,\n" +
|
||||
" \"start_time\": \"3022.022404\",\n" +
|
||||
" \"end\": 83768105,\n" +
|
||||
" \"end_time\": \"3799.007029\",\n" +
|
||||
" \"tags\": {\n" +
|
||||
" \"title\": \"11 The Nine Situations\"\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"id\": 6,\n" +
|
||||
" \"time_base\": \"1/22050\",\n" +
|
||||
" \"start\": 83768105,\n" +
|
||||
" \"start_time\": \"3799.007029\",\n" +
|
||||
" \"end\": 95659008,\n" +
|
||||
" \"end_time\": \"4338.277007\",\n" +
|
||||
" \"tags\": {\n" +
|
||||
" \"title\": \"12 The Attack By Fire - 13 The Use of Spies\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" ],\n" +
|
||||
" \"format\": {\n" +
|
||||
" \"filename\": \"sample.mp3\",\n" +
|
||||
" \"nb_streams\": 1,\n" +
|
||||
" \"nb_programs\": 0,\n" +
|
||||
" \"format_name\": \"mp3\",\n" +
|
||||
" \"format_long_name\": \"MP2/3 (MPEG audio layer 2/3)\",\n" +
|
||||
" \"start_time\": \"0.011995\",\n" +
|
||||
" \"duration\": \"327.549388\",\n" +
|
||||
" \"size\": \"13103064\",\n" +
|
||||
" \"bit_rate\": \"320026\",\n" +
|
||||
" \"probe_score\": 51,\n" +
|
||||
" \"tags\": {\n" +
|
||||
" \"encoder\": \"Lavf58.20.100\",\n" +
|
||||
" \"album\": \"Impact\",\n" +
|
||||
" \"artist\": \"Kevin MacLeod\",\n" +
|
||||
" \"comment\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit finito.\",\n" +
|
||||
" \"genre\": \"Cinematic\",\n" +
|
||||
" \"title\": \"Impact Moderato\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
|
||||
private static final String MEDIA_INFORMATION_JPG =
|
||||
"{\n" +
|
||||
" \"streams\": [\n" +
|
||||
" {\n" +
|
||||
" \"index\": 0,\n" +
|
||||
" \"codec_name\": \"mjpeg\",\n" +
|
||||
" \"codec_long_name\": \"Motion JPEG\",\n" +
|
||||
" \"profile\": \"Baseline\",\n" +
|
||||
" \"codec_type\": \"video\",\n" +
|
||||
" \"codec_time_base\": \"0/1\",\n" +
|
||||
" \"codec_tag_string\": \"[0][0][0][0]\",\n" +
|
||||
" \"codec_tag\": \"0x0000\",\n" +
|
||||
" \"width\": 1496,\n" +
|
||||
" \"height\": 1729,\n" +
|
||||
" \"coded_width\": 1496,\n" +
|
||||
" \"coded_height\": 1729,\n" +
|
||||
" \"has_b_frames\": 0,\n" +
|
||||
" \"sample_aspect_ratio\": \"1:1\",\n" +
|
||||
" \"display_aspect_ratio\": \"1496:1729\",\n" +
|
||||
" \"pix_fmt\": \"yuvj444p\",\n" +
|
||||
" \"level\": -99,\n" +
|
||||
" \"color_range\": \"pc\",\n" +
|
||||
" \"color_space\": \"bt470bg\",\n" +
|
||||
" \"chroma_location\": \"center\",\n" +
|
||||
" \"refs\": 1,\n" +
|
||||
" \"r_frame_rate\": \"25/1\",\n" +
|
||||
" \"avg_frame_rate\": \"0/0\",\n" +
|
||||
" \"time_base\": \"1/25\",\n" +
|
||||
" \"start_pts\": 0,\n" +
|
||||
" \"start_time\": \"0.000000\",\n" +
|
||||
" \"duration_ts\": 1,\n" +
|
||||
" \"duration\": \"0.040000\",\n" +
|
||||
" \"bits_per_raw_sample\": \"8\",\n" +
|
||||
" \"disposition\": {\n" +
|
||||
" \"default\": 0,\n" +
|
||||
" \"dub\": 0,\n" +
|
||||
" \"original\": 0,\n" +
|
||||
" \"comment\": 0,\n" +
|
||||
" \"lyrics\": 0,\n" +
|
||||
" \"karaoke\": 0,\n" +
|
||||
" \"forced\": 0,\n" +
|
||||
" \"hearing_impaired\": 0,\n" +
|
||||
" \"visual_impaired\": 0,\n" +
|
||||
" \"clean_effects\": 0,\n" +
|
||||
" \"attached_pic\": 0,\n" +
|
||||
" \"timed_thumbnails\": 0\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" ],\n" +
|
||||
" \"format\": {\n" +
|
||||
" \"filename\": \"sample.jpg\",\n" +
|
||||
" \"nb_streams\": 1,\n" +
|
||||
" \"nb_programs\": 0,\n" +
|
||||
" \"format_name\": \"image2\",\n" +
|
||||
" \"format_long_name\": \"image2 sequence\",\n" +
|
||||
" \"start_time\": \"0.000000\",\n" +
|
||||
" \"duration\": \"0.040000\",\n" +
|
||||
" \"size\": \"1659050\",\n" +
|
||||
" \"bit_rate\": \"331810000\",\n" +
|
||||
" \"probe_score\": 50\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
|
||||
private static final String MEDIA_INFORMATION_GIF =
|
||||
"{\n" +
|
||||
" \"streams\": [\n" +
|
||||
" {\n" +
|
||||
" \"index\": 0,\n" +
|
||||
" \"codec_name\": \"gif\",\n" +
|
||||
" \"codec_long_name\": \"CompuServe GIF (Graphics Interchange Format)\",\n" +
|
||||
" \"codec_type\": \"video\",\n" +
|
||||
" \"codec_time_base\": \"12/133\",\n" +
|
||||
" \"codec_tag_string\": \"[0][0][0][0]\",\n" +
|
||||
" \"codec_tag\": \"0x0000\",\n" +
|
||||
" \"width\": 400,\n" +
|
||||
" \"height\": 400,\n" +
|
||||
" \"coded_width\": 400,\n" +
|
||||
" \"coded_height\": 400,\n" +
|
||||
" \"has_b_frames\": 0,\n" +
|
||||
" \"pix_fmt\": \"bgra\",\n" +
|
||||
" \"level\": -99,\n" +
|
||||
" \"refs\": 1,\n" +
|
||||
" \"r_frame_rate\": \"100/9\",\n" +
|
||||
" \"avg_frame_rate\": \"133/12\",\n" +
|
||||
" \"time_base\": \"1/100\",\n" +
|
||||
" \"start_pts\": 0,\n" +
|
||||
" \"start_time\": \"0.000000\",\n" +
|
||||
" \"duration_ts\": 396,\n" +
|
||||
" \"duration\": \"3.960000\",\n" +
|
||||
" \"nb_frames\": \"44\",\n" +
|
||||
" \"disposition\": {\n" +
|
||||
" \"default\": 0,\n" +
|
||||
" \"dub\": 0,\n" +
|
||||
" \"original\": 0,\n" +
|
||||
" \"comment\": 0,\n" +
|
||||
" \"lyrics\": 0,\n" +
|
||||
" \"karaoke\": 0,\n" +
|
||||
" \"forced\": 0,\n" +
|
||||
" \"hearing_impaired\": 0,\n" +
|
||||
" \"visual_impaired\": 0,\n" +
|
||||
" \"clean_effects\": 0,\n" +
|
||||
" \"attached_pic\": 0,\n" +
|
||||
" \"timed_thumbnails\": 0\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" ],\n" +
|
||||
" \"format\": {\n" +
|
||||
" \"filename\": \"sample.gif\",\n" +
|
||||
" \"nb_streams\": 1,\n" +
|
||||
" \"nb_programs\": 0,\n" +
|
||||
" \"format_name\": \"gif\",\n" +
|
||||
" \"format_long_name\": \"CompuServe Graphics Interchange Format (GIF)\",\n" +
|
||||
" \"start_time\": \"0.000000\",\n" +
|
||||
" \"duration\": \"3.960000\",\n" +
|
||||
" \"size\": \"1001718\",\n" +
|
||||
" \"bit_rate\": \"2023672\",\n" +
|
||||
" \"probe_score\": 100\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
|
||||
private static final String MEDIA_INFORMATION_MP4 =
|
||||
"{\n" +
|
||||
" \"streams\": [\n" +
|
||||
" {\n" +
|
||||
" \"index\": 0,\n" +
|
||||
" \"codec_name\": \"h264\",\n" +
|
||||
" \"codec_long_name\": \"H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10\",\n" +
|
||||
" \"profile\": \"Main\",\n" +
|
||||
" \"codec_type\": \"video\",\n" +
|
||||
" \"codec_time_base\": \"1/60\",\n" +
|
||||
" \"codec_tag_string\": \"avc1\",\n" +
|
||||
" \"codec_tag\": \"0x31637661\",\n" +
|
||||
" \"width\": 1280,\n" +
|
||||
" \"height\": 720,\n" +
|
||||
" \"coded_width\": 1280,\n" +
|
||||
" \"coded_height\": 720,\n" +
|
||||
" \"has_b_frames\": 0,\n" +
|
||||
" \"sample_aspect_ratio\": \"1:1\",\n" +
|
||||
" \"display_aspect_ratio\": \"16:9\",\n" +
|
||||
" \"pix_fmt\": \"yuv420p\",\n" +
|
||||
" \"level\": 42,\n" +
|
||||
" \"chroma_location\": \"left\",\n" +
|
||||
" \"refs\": 1,\n" +
|
||||
" \"is_avc\": \"true\",\n" +
|
||||
" \"nal_length_size\": \"4\",\n" +
|
||||
" \"r_frame_rate\": \"30/1\",\n" +
|
||||
" \"avg_frame_rate\": \"30/1\",\n" +
|
||||
" \"time_base\": \"1/15360\",\n" +
|
||||
" \"start_pts\": 0,\n" +
|
||||
" \"start_time\": \"0.000000\",\n" +
|
||||
" \"duration_ts\": 215040,\n" +
|
||||
" \"duration\": \"14.000000\",\n" +
|
||||
" \"bit_rate\": \"9166570\",\n" +
|
||||
" \"bits_per_raw_sample\": \"8\",\n" +
|
||||
" \"nb_frames\": \"420\",\n" +
|
||||
" \"disposition\": {\n" +
|
||||
" \"default\": 1,\n" +
|
||||
" \"dub\": 0,\n" +
|
||||
" \"original\": 0,\n" +
|
||||
" \"comment\": 0,\n" +
|
||||
" \"lyrics\": 0,\n" +
|
||||
" \"karaoke\": 0,\n" +
|
||||
" \"forced\": 0,\n" +
|
||||
" \"hearing_impaired\": 0,\n" +
|
||||
" \"visual_impaired\": 0,\n" +
|
||||
" \"clean_effects\": 0,\n" +
|
||||
" \"attached_pic\": 0,\n" +
|
||||
" \"timed_thumbnails\": 0\n" +
|
||||
" },\n" +
|
||||
" \"tags\": {\n" +
|
||||
" \"language\": \"und\",\n" +
|
||||
" \"handler_name\": \"VideoHandler\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" ],\n" +
|
||||
" \"format\": {\n" +
|
||||
" \"filename\": \"sample.mp4\",\n" +
|
||||
" \"nb_streams\": 1,\n" +
|
||||
" \"nb_programs\": 0,\n" +
|
||||
" \"format_name\": \"mov,mp4,m4a,3gp,3g2,mj2\",\n" +
|
||||
" \"format_long_name\": \"QuickTime / MOV\",\n" +
|
||||
" \"start_time\": \"0.000000\",\n" +
|
||||
" \"duration\": \"14.000000\",\n" +
|
||||
" \"size\": \"16044159\",\n" +
|
||||
" \"bit_rate\": \"9168090\",\n" +
|
||||
" \"probe_score\": 100,\n" +
|
||||
" \"tags\": {\n" +
|
||||
" \"major_brand\": \"isom\",\n" +
|
||||
" \"minor_version\": \"512\",\n" +
|
||||
" \"compatible_brands\": \"isomiso2avc1mp41\",\n" +
|
||||
" \"encoder\": \"Lavf58.33.100\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
|
||||
private static final String MEDIA_INFORMATION_PNG =
|
||||
"{\n" +
|
||||
" \"streams\": [\n" +
|
||||
" {\n" +
|
||||
" \"index\": 0,\n" +
|
||||
" \"codec_name\": \"png\",\n" +
|
||||
" \"codec_long_name\": \"PNG (Portable Network Graphics) image\",\n" +
|
||||
" \"codec_type\": \"video\",\n" +
|
||||
" \"codec_time_base\": \"0/1\",\n" +
|
||||
" \"codec_tag_string\": \"[0][0][0][0]\",\n" +
|
||||
" \"codec_tag\": \"0x0000\",\n" +
|
||||
" \"width\": 1198,\n" +
|
||||
" \"height\": 1198,\n" +
|
||||
" \"coded_width\": 1198,\n" +
|
||||
" \"coded_height\": 1198,\n" +
|
||||
" \"has_b_frames\": 0,\n" +
|
||||
" \"sample_aspect_ratio\": \"1:1\",\n" +
|
||||
" \"display_aspect_ratio\": \"1:1\",\n" +
|
||||
" \"pix_fmt\": \"pal8\",\n" +
|
||||
" \"level\": -99,\n" +
|
||||
" \"color_range\": \"pc\",\n" +
|
||||
" \"refs\": 1,\n" +
|
||||
" \"r_frame_rate\": \"25/1\",\n" +
|
||||
" \"avg_frame_rate\": \"0/0\",\n" +
|
||||
" \"time_base\": \"1/25\",\n" +
|
||||
" \"disposition\": {\n" +
|
||||
" \"default\": 0,\n" +
|
||||
" \"dub\": 0,\n" +
|
||||
" \"original\": 0,\n" +
|
||||
" \"comment\": 0,\n" +
|
||||
" \"lyrics\": 0,\n" +
|
||||
" \"karaoke\": 0,\n" +
|
||||
" \"forced\": 0,\n" +
|
||||
" \"hearing_impaired\": 0,\n" +
|
||||
" \"visual_impaired\": 0,\n" +
|
||||
" \"clean_effects\": 0,\n" +
|
||||
" \"attached_pic\": 0,\n" +
|
||||
" \"timed_thumbnails\": 0\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" ],\n" +
|
||||
" \"format\": {\n" +
|
||||
" \"filename\": \"sample.png\",\n" +
|
||||
" \"nb_streams\": 1,\n" +
|
||||
" \"nb_programs\": 0,\n" +
|
||||
" \"format_name\": \"png_pipe\",\n" +
|
||||
" \"format_long_name\": \"piped png sequence\",\n" +
|
||||
" \"size\": \"31533\",\n" +
|
||||
" \"probe_score\": 99\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
|
||||
private static final String MEDIA_INFORMATION_OGG =
|
||||
"{\n" +
|
||||
" \"streams\": [\n" +
|
||||
" {\n" +
|
||||
" \"index\": 0,\n" +
|
||||
" \"codec_name\": \"theora\",\n" +
|
||||
" \"codec_long_name\": \"Theora\",\n" +
|
||||
" \"codec_type\": \"video\",\n" +
|
||||
" \"codec_time_base\": \"1/25\",\n" +
|
||||
" \"codec_tag_string\": \"[0][0][0][0]\",\n" +
|
||||
" \"codec_tag\": \"0x0000\",\n" +
|
||||
" \"width\": 1920,\n" +
|
||||
" \"height\": 1080,\n" +
|
||||
" \"coded_width\": 1920,\n" +
|
||||
" \"coded_height\": 1088,\n" +
|
||||
" \"has_b_frames\": 0,\n" +
|
||||
" \"pix_fmt\": \"yuv420p\",\n" +
|
||||
" \"level\": -99,\n" +
|
||||
" \"color_space\": \"bt470bg\",\n" +
|
||||
" \"color_transfer\": \"bt709\",\n" +
|
||||
" \"color_primaries\": \"bt470bg\",\n" +
|
||||
" \"chroma_location\": \"center\",\n" +
|
||||
" \"refs\": 1,\n" +
|
||||
" \"r_frame_rate\": \"25/1\",\n" +
|
||||
" \"avg_frame_rate\": \"25/1\",\n" +
|
||||
" \"time_base\": \"1/25\",\n" +
|
||||
" \"start_pts\": 0,\n" +
|
||||
" \"start_time\": \"0.000000\",\n" +
|
||||
" \"duration_ts\": 813,\n" +
|
||||
" \"duration\": \"32.520000\",\n" +
|
||||
" \"disposition\": {\n" +
|
||||
" \"default\": 0,\n" +
|
||||
" \"dub\": 0,\n" +
|
||||
" \"original\": 0,\n" +
|
||||
" \"comment\": 0,\n" +
|
||||
" \"lyrics\": 0,\n" +
|
||||
" \"karaoke\": 0,\n" +
|
||||
" \"forced\": 0,\n" +
|
||||
" \"hearing_impaired\": 0,\n" +
|
||||
" \"visual_impaired\": 0,\n" +
|
||||
" \"clean_effects\": 0,\n" +
|
||||
" \"attached_pic\": 0,\n" +
|
||||
" \"timed_thumbnails\": 0\n" +
|
||||
" },\n" +
|
||||
" \"tags\": {\n" +
|
||||
" \"ENCODER\": \"ffmpeg2theora 0.19\"\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"index\": 1,\n" +
|
||||
" \"codec_name\": \"vorbis\",\n" +
|
||||
" \"codec_long_name\": \"Vorbis\",\n" +
|
||||
" \"codec_type\": \"audio\",\n" +
|
||||
" \"codec_time_base\": \"1/48000\",\n" +
|
||||
" \"codec_tag_string\": \"[0][0][0][0]\",\n" +
|
||||
" \"codec_tag\": \"0x0000\",\n" +
|
||||
" \"sample_fmt\": \"fltp\",\n" +
|
||||
" \"sample_rate\": \"48000\",\n" +
|
||||
" \"channels\": 2,\n" +
|
||||
" \"channel_layout\": \"stereo\",\n" +
|
||||
" \"bits_per_sample\": 0,\n" +
|
||||
" \"r_frame_rate\": \"0/0\",\n" +
|
||||
" \"avg_frame_rate\": \"0/0\",\n" +
|
||||
" \"time_base\": \"1/48000\",\n" +
|
||||
" \"start_pts\": 0,\n" +
|
||||
" \"start_time\": \"0.000000\",\n" +
|
||||
" \"duration_ts\": 1583850,\n" +
|
||||
" \"duration\": \"32.996875\",\n" +
|
||||
" \"bit_rate\": \"80000\",\n" +
|
||||
" \"disposition\": {\n" +
|
||||
" \"default\": 0,\n" +
|
||||
" \"dub\": 0,\n" +
|
||||
" \"original\": 0,\n" +
|
||||
" \"comment\": 0,\n" +
|
||||
" \"lyrics\": 0,\n" +
|
||||
" \"karaoke\": 0,\n" +
|
||||
" \"forced\": 0,\n" +
|
||||
" \"hearing_impaired\": 0,\n" +
|
||||
" \"visual_impaired\": 0,\n" +
|
||||
" \"clean_effects\": 0,\n" +
|
||||
" \"attached_pic\": 0,\n" +
|
||||
" \"timed_thumbnails\": 0\n" +
|
||||
" },\n" +
|
||||
" \"tags\": {\n" +
|
||||
" \"ENCODER\": \"ffmpeg2theora 0.19\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" ],\n" +
|
||||
" \"format\": {\n" +
|
||||
" \"filename\": \"sample.ogg\",\n" +
|
||||
" \"nb_streams\": 2,\n" +
|
||||
" \"nb_programs\": 0,\n" +
|
||||
" \"format_name\": \"ogg\",\n" +
|
||||
" \"format_long_name\": \"Ogg\",\n" +
|
||||
" \"start_time\": \"0.000000\",\n" +
|
||||
" \"duration\": \"32.996875\",\n" +
|
||||
" \"size\": \"27873937\",\n" +
|
||||
" \"bit_rate\": \"6757958\",\n" +
|
||||
" \"probe_score\": 100\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
|
||||
@Test
|
||||
public void mediaInformationMp3() {
|
||||
MediaInformation mediaInformation = MediaInformationJsonParser.from(MEDIA_INFORMATION_MP3);
|
||||
|
||||
Assert.assertNotNull(mediaInformation);
|
||||
assertMediaInput(mediaInformation, "mp3", "sample.mp3");
|
||||
assertMediaDuration(mediaInformation, "327.549388", "0.011995", "320026");
|
||||
|
||||
assertTag(mediaInformation, "comment", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit finito.");
|
||||
assertTag(mediaInformation, "album", "Impact");
|
||||
assertTag(mediaInformation, "title", "Impact Moderato");
|
||||
assertTag(mediaInformation, "artist", "Kevin MacLeod");
|
||||
|
||||
Assert.assertNotNull(mediaInformation.getStreams());
|
||||
Assert.assertEquals(1, mediaInformation.getStreams().size());
|
||||
assertAudioStream(mediaInformation.getStreams().get(0), 0L, "mp3", "MP3 (MPEG audio layer 3)", "44100", "stereo", "fltp", "320000");
|
||||
|
||||
Assert.assertNotNull(mediaInformation.getChapters());
|
||||
Assert.assertEquals(7, mediaInformation.getChapters().size());
|
||||
assertChapter(mediaInformation.getChapters().get(0), 0L, "1/22050", 0L, "0.000000", 11158238L, "506.042540");
|
||||
assertChapter(mediaInformation.getChapters().get(1), 1L, "1/22050", 11158238L, "506.042540", 21433051L, "972.020454");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mediaInformationJpg() {
|
||||
MediaInformation mediaInformation = MediaInformationJsonParser.from(MEDIA_INFORMATION_JPG);
|
||||
|
||||
Assert.assertNotNull(mediaInformation);
|
||||
assertMediaInput(mediaInformation, "image2", "sample.jpg");
|
||||
assertMediaDuration(mediaInformation, "0.040000", "0.000000", "331810000");
|
||||
Assert.assertNotNull(mediaInformation.getStreams());
|
||||
Assert.assertEquals(1, mediaInformation.getStreams().size());
|
||||
assertVideoStream(mediaInformation.getStreams().get(0), 0L, "mjpeg", "Motion JPEG", "yuvj444p", 1496L, 1729L, "1:1", "1496:1729", null, "0/0", "25/1", "1/25", "0/1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mediaInformationGif() {
|
||||
MediaInformation mediaInformation = MediaInformationJsonParser.from(MEDIA_INFORMATION_GIF);
|
||||
|
||||
Assert.assertNotNull(mediaInformation);
|
||||
assertMediaInput(mediaInformation, "gif", "sample.gif");
|
||||
assertMediaDuration(mediaInformation, "3.960000", "0.000000", "2023672");
|
||||
Assert.assertNotNull(mediaInformation.getStreams());
|
||||
Assert.assertEquals(1, mediaInformation.getStreams().size());
|
||||
assertVideoStream(mediaInformation.getStreams().get(0), 0L, "gif", "CompuServe GIF (Graphics Interchange Format)", "bgra", 400L, 400L, null, null, null, "133/12", "100/9", "1/100", "12/133");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mediaInformationMp4() {
|
||||
MediaInformation mediaInformation = MediaInformationJsonParser.from(MEDIA_INFORMATION_MP4);
|
||||
|
||||
Assert.assertNotNull(mediaInformation);
|
||||
assertMediaInput(mediaInformation, "mov,mp4,m4a,3gp,3g2,mj2", "sample.mp4");
|
||||
assertMediaDuration(mediaInformation, "14.000000", "0.000000", "9168090");
|
||||
|
||||
assertTag(mediaInformation, "major_brand", "isom");
|
||||
assertTag(mediaInformation, "minor_version", "512");
|
||||
assertTag(mediaInformation, "compatible_brands", "isomiso2avc1mp41");
|
||||
assertTag(mediaInformation, "encoder", "Lavf58.33.100");
|
||||
|
||||
Assert.assertNotNull(mediaInformation.getStreams());
|
||||
Assert.assertEquals(1, mediaInformation.getStreams().size());
|
||||
assertVideoStream(mediaInformation.getStreams().get(0), 0L, "h264", "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", "yuv420p", 1280L, 720L, "1:1", "16:9", "9166570", "30/1", "30/1", "1/15360", "1/60");
|
||||
|
||||
assertStreamTag(mediaInformation.getStreams().get(0), "language", "und");
|
||||
assertStreamTag(mediaInformation.getStreams().get(0), "handler_name", "VideoHandler");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mediaInformationPng() {
|
||||
MediaInformation mediaInformation = MediaInformationJsonParser.from(MEDIA_INFORMATION_PNG);
|
||||
|
||||
Assert.assertNotNull(mediaInformation);
|
||||
assertMediaInput(mediaInformation, "png_pipe", "sample.png");
|
||||
assertMediaDuration(mediaInformation, null, null, null);
|
||||
Assert.assertNotNull(mediaInformation.getStreams());
|
||||
Assert.assertEquals(1, mediaInformation.getStreams().size());
|
||||
assertVideoStream(mediaInformation.getStreams().get(0), 0L, "png", "PNG (Portable Network Graphics) image", "pal8", 1198L, 1198L, "1:1", "1:1", null, "0/0", "25/1", "1/25", "0/1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mediaInformationOgg() {
|
||||
MediaInformation mediaInformation = MediaInformationJsonParser.from(MEDIA_INFORMATION_OGG);
|
||||
|
||||
Assert.assertNotNull(mediaInformation);
|
||||
assertMediaInput(mediaInformation, "ogg", "sample.ogg");
|
||||
assertMediaDuration(mediaInformation, "32.996875", "0.000000", "6757958");
|
||||
Assert.assertNotNull(mediaInformation.getStreams());
|
||||
Assert.assertEquals(2, mediaInformation.getStreams().size());
|
||||
assertVideoStream(mediaInformation.getStreams().get(0), 0L, "theora", "Theora", "yuv420p", 1920L, 1080L, null, null, null, "25/1", "25/1", "1/25", "1/25");
|
||||
assertAudioStream(mediaInformation.getStreams().get(1), 1L, "vorbis", "Vorbis", "48000", "stereo", "fltp", "80000");
|
||||
|
||||
assertStreamTag(mediaInformation.getStreams().get(0), "ENCODER", "ffmpeg2theora 0.19");
|
||||
assertStreamTag(mediaInformation.getStreams().get(1), "ENCODER", "ffmpeg2theora 0.19");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSimpleCommand() {
|
||||
final String[] argumentArray = FFmpegKitConfig.parseArguments("-hide_banner -loop 1 -i file.jpg -filter_complex [0:v]setpts=PTS-STARTPTS[video] -map [video] -vsync 2 -async 1 video.mp4");
|
||||
|
||||
Assert.assertNotNull(argumentArray);
|
||||
Assert.assertEquals(14, argumentArray.length);
|
||||
|
||||
Assert.assertEquals("-hide_banner", argumentArray[0]);
|
||||
Assert.assertEquals("-loop", argumentArray[1]);
|
||||
Assert.assertEquals("1", argumentArray[2]);
|
||||
Assert.assertEquals("-i", argumentArray[3]);
|
||||
Assert.assertEquals("file.jpg", argumentArray[4]);
|
||||
Assert.assertEquals("-filter_complex", argumentArray[5]);
|
||||
Assert.assertEquals("[0:v]setpts=PTS-STARTPTS[video]", argumentArray[6]);
|
||||
Assert.assertEquals("-map", argumentArray[7]);
|
||||
Assert.assertEquals("[video]", argumentArray[8]);
|
||||
Assert.assertEquals("-vsync", argumentArray[9]);
|
||||
Assert.assertEquals("2", argumentArray[10]);
|
||||
Assert.assertEquals("-async", argumentArray[11]);
|
||||
Assert.assertEquals("1", argumentArray[12]);
|
||||
Assert.assertEquals("video.mp4", argumentArray[13]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSingleQuotesInCommand() {
|
||||
String[] argumentArray = FFmpegKitConfig.parseArguments("-loop 1 'file one.jpg' -filter_complex '[0:v]setpts=PTS-STARTPTS[video]' -map [video] video.mp4 ");
|
||||
|
||||
Assert.assertNotNull(argumentArray);
|
||||
Assert.assertEquals(8, argumentArray.length);
|
||||
|
||||
Assert.assertEquals("-loop", argumentArray[0]);
|
||||
Assert.assertEquals("1", argumentArray[1]);
|
||||
Assert.assertEquals("file one.jpg", argumentArray[2]);
|
||||
Assert.assertEquals("-filter_complex", argumentArray[3]);
|
||||
Assert.assertEquals("[0:v]setpts=PTS-STARTPTS[video]", argumentArray[4]);
|
||||
Assert.assertEquals("-map", argumentArray[5]);
|
||||
Assert.assertEquals("[video]", argumentArray[6]);
|
||||
Assert.assertEquals("video.mp4", argumentArray[7]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseDoubleQuotesInCommand() {
|
||||
String[] argumentArray = FFmpegKitConfig.parseArguments("-loop 1 \"file one.jpg\" -filter_complex \"[0:v]setpts=PTS-STARTPTS[video]\" -map [video] video.mp4 ");
|
||||
|
||||
Assert.assertNotNull(argumentArray);
|
||||
Assert.assertEquals(8, argumentArray.length);
|
||||
|
||||
Assert.assertEquals("-loop", argumentArray[0]);
|
||||
Assert.assertEquals("1", argumentArray[1]);
|
||||
Assert.assertEquals("file one.jpg", argumentArray[2]);
|
||||
Assert.assertEquals("-filter_complex", argumentArray[3]);
|
||||
Assert.assertEquals("[0:v]setpts=PTS-STARTPTS[video]", argumentArray[4]);
|
||||
Assert.assertEquals("-map", argumentArray[5]);
|
||||
Assert.assertEquals("[video]", argumentArray[6]);
|
||||
Assert.assertEquals("video.mp4", argumentArray[7]);
|
||||
|
||||
argumentArray = FFmpegKitConfig.parseArguments(" -i file:///tmp/input.mp4 -vcodec libx264 -vf \"scale=1024:1024,pad=width=1024:height=1024:x=0:y=0:color=black\" -acodec copy -q:v 0 -q:a 0 video.mp4");
|
||||
|
||||
Assert.assertNotNull(argumentArray);
|
||||
Assert.assertEquals(13, argumentArray.length);
|
||||
|
||||
Assert.assertEquals("-i", argumentArray[0]);
|
||||
Assert.assertEquals("file:///tmp/input.mp4", argumentArray[1]);
|
||||
Assert.assertEquals("-vcodec", argumentArray[2]);
|
||||
Assert.assertEquals("libx264", argumentArray[3]);
|
||||
Assert.assertEquals("-vf", argumentArray[4]);
|
||||
Assert.assertEquals("scale=1024:1024,pad=width=1024:height=1024:x=0:y=0:color=black", argumentArray[5]);
|
||||
Assert.assertEquals("-acodec", argumentArray[6]);
|
||||
Assert.assertEquals("copy", argumentArray[7]);
|
||||
Assert.assertEquals("-q:v", argumentArray[8]);
|
||||
Assert.assertEquals("0", argumentArray[9]);
|
||||
Assert.assertEquals("-q:a", argumentArray[10]);
|
||||
Assert.assertEquals("0", argumentArray[11]);
|
||||
Assert.assertEquals("video.mp4", argumentArray[12]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseDoubleQuotesAndEscapesInCommand() {
|
||||
String[] argumentArray = FFmpegKitConfig.parseArguments(" -i file:///tmp/input.mp4 -vf \"subtitles=file:///tmp/subtitles.srt:force_style=\'FontSize=16,PrimaryColour=&HFFFFFF&\'\" -vcodec libx264 -acodec copy -q:v 0 -q:a 0 video.mp4");
|
||||
|
||||
Assert.assertNotNull(argumentArray);
|
||||
Assert.assertEquals(13, argumentArray.length);
|
||||
|
||||
Assert.assertEquals("-i", argumentArray[0]);
|
||||
Assert.assertEquals("file:///tmp/input.mp4", argumentArray[1]);
|
||||
Assert.assertEquals("-vf", argumentArray[2]);
|
||||
Assert.assertEquals("subtitles=file:///tmp/subtitles.srt:force_style='FontSize=16,PrimaryColour=&HFFFFFF&'", argumentArray[3]);
|
||||
Assert.assertEquals("-vcodec", argumentArray[4]);
|
||||
Assert.assertEquals("libx264", argumentArray[5]);
|
||||
Assert.assertEquals("-acodec", argumentArray[6]);
|
||||
Assert.assertEquals("copy", argumentArray[7]);
|
||||
Assert.assertEquals("-q:v", argumentArray[8]);
|
||||
Assert.assertEquals("0", argumentArray[9]);
|
||||
Assert.assertEquals("-q:a", argumentArray[10]);
|
||||
Assert.assertEquals("0", argumentArray[11]);
|
||||
Assert.assertEquals("video.mp4", argumentArray[12]);
|
||||
|
||||
argumentArray = FFmpegKitConfig.parseArguments(" -i file:///tmp/input.mp4 -vf \"subtitles=file:///tmp/subtitles.srt:force_style=\\\"FontSize=16,PrimaryColour=&HFFFFFF&\\\"\" -vcodec libx264 -acodec copy -q:v 0 -q:a 0 video.mp4");
|
||||
|
||||
Assert.assertNotNull(argumentArray);
|
||||
Assert.assertEquals(13, argumentArray.length);
|
||||
|
||||
Assert.assertEquals("-i", argumentArray[0]);
|
||||
Assert.assertEquals("file:///tmp/input.mp4", argumentArray[1]);
|
||||
Assert.assertEquals("-vf", argumentArray[2]);
|
||||
Assert.assertEquals("subtitles=file:///tmp/subtitles.srt:force_style=\\\"FontSize=16,PrimaryColour=&HFFFFFF&\\\"", argumentArray[3]);
|
||||
Assert.assertEquals("-vcodec", argumentArray[4]);
|
||||
Assert.assertEquals("libx264", argumentArray[5]);
|
||||
Assert.assertEquals("-acodec", argumentArray[6]);
|
||||
Assert.assertEquals("copy", argumentArray[7]);
|
||||
Assert.assertEquals("-q:v", argumentArray[8]);
|
||||
Assert.assertEquals("0", argumentArray[9]);
|
||||
Assert.assertEquals("-q:a", argumentArray[10]);
|
||||
Assert.assertEquals("0", argumentArray[11]);
|
||||
Assert.assertEquals("video.mp4", argumentArray[12]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void argumentsToString() {
|
||||
Assert.assertEquals("null", argumentsToString(null));
|
||||
Assert.assertEquals("-i input.mp4 -vf filter -c:v mpeg4 output.mp4", argumentsToString(new String[]{"-i", "input.mp4", "-vf", "filter", "-c:v", "mpeg4", "output.mp4"}));
|
||||
}
|
||||
|
||||
public String argumentsToString(final String[] arguments) {
|
||||
return FFmpegKitConfig.argumentsToString(arguments);
|
||||
}
|
||||
|
||||
private void assertMediaInput(MediaInformation mediaInformation, String format, String filename) {
|
||||
Assert.assertEquals(format, mediaInformation.getFormat());
|
||||
Assert.assertEquals(filename, mediaInformation.getFilename());
|
||||
}
|
||||
|
||||
private void assertMediaDuration(MediaInformation mediaInformation, String duration, String startTime, String bitrate) {
|
||||
Assert.assertEquals(duration, mediaInformation.getDuration());
|
||||
Assert.assertEquals(startTime, mediaInformation.getStartTime());
|
||||
Assert.assertEquals(bitrate, mediaInformation.getBitrate());
|
||||
}
|
||||
|
||||
private void assertTag(MediaInformation mediaInformation, String expectedKey, String expectedValue) {
|
||||
JSONObject tags = mediaInformation.getTags();
|
||||
Assert.assertNotNull(tags);
|
||||
|
||||
try {
|
||||
String value = tags.getString(expectedKey);
|
||||
Assert.assertEquals(expectedValue, value);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
Assert.fail(expectedKey + " not found");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertStreamTag(StreamInformation streamInformation, String expectedKey, String expectedValue) {
|
||||
JSONObject tags = streamInformation.getTags();
|
||||
Assert.assertNotNull(tags);
|
||||
|
||||
try {
|
||||
String value = tags.getString(expectedKey);
|
||||
Assert.assertEquals(expectedValue, value);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
Assert.fail(expectedKey + " not found");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertStream(StreamInformation streamInformation, Long index, String type, String codec, String fullCodec, String bitrate) {
|
||||
Assert.assertEquals(index, streamInformation.getIndex());
|
||||
Assert.assertEquals(type, streamInformation.getType());
|
||||
|
||||
Assert.assertEquals(codec, streamInformation.getCodec());
|
||||
Assert.assertEquals(fullCodec, streamInformation.getCodecLong());
|
||||
|
||||
Assert.assertEquals(bitrate, streamInformation.getBitrate());
|
||||
}
|
||||
|
||||
private void assertAudioStream(StreamInformation streamInformation, Long index, String codec, String fullCodec, String sampleRate, String channelLayout, String sampleFormat, String bitrate) {
|
||||
Assert.assertEquals(index, streamInformation.getIndex());
|
||||
Assert.assertEquals("audio", streamInformation.getType());
|
||||
|
||||
Assert.assertEquals(codec, streamInformation.getCodec());
|
||||
Assert.assertEquals(fullCodec, streamInformation.getCodecLong());
|
||||
|
||||
Assert.assertEquals(sampleRate, streamInformation.getSampleRate());
|
||||
Assert.assertEquals(channelLayout, streamInformation.getChannelLayout());
|
||||
Assert.assertEquals(sampleFormat, streamInformation.getSampleFormat());
|
||||
Assert.assertEquals(bitrate, streamInformation.getBitrate());
|
||||
}
|
||||
|
||||
private void assertVideoStream(StreamInformation streamInformation, Long index, String codec, String fullCodec, String format, Long width, Long height, String sar, String dar, String bitrate, String averageFrameRate, String realFrameRate, String timeBase, String codecTimeBase) {
|
||||
Assert.assertEquals(index, streamInformation.getIndex());
|
||||
Assert.assertEquals("video", streamInformation.getType());
|
||||
|
||||
Assert.assertEquals(codec, streamInformation.getCodec());
|
||||
Assert.assertEquals(fullCodec, streamInformation.getCodecLong());
|
||||
|
||||
Assert.assertEquals(format, streamInformation.getFormat());
|
||||
|
||||
Assert.assertEquals(width, streamInformation.getWidth());
|
||||
Assert.assertEquals(height, streamInformation.getHeight());
|
||||
Assert.assertEquals(sar, streamInformation.getSampleAspectRatio());
|
||||
Assert.assertEquals(dar, streamInformation.getDisplayAspectRatio());
|
||||
|
||||
Assert.assertEquals(bitrate, streamInformation.getBitrate());
|
||||
|
||||
Assert.assertEquals(averageFrameRate, streamInformation.getAverageFrameRate());
|
||||
Assert.assertEquals(realFrameRate, streamInformation.getRealFrameRate());
|
||||
Assert.assertEquals(timeBase, streamInformation.getTimeBase());
|
||||
Assert.assertEquals(codecTimeBase, streamInformation.getCodecTimeBase());
|
||||
}
|
||||
|
||||
private void assertChapter(Chapter chapter, Long id, String timeBase, Long start, String startTime, Long end, String endTime) {
|
||||
Assert.assertEquals(id, chapter.getId());
|
||||
Assert.assertEquals(timeBase, chapter.getTimeBase());
|
||||
|
||||
Assert.assertEquals(start, chapter.getStart());
|
||||
Assert.assertEquals(startTime, chapter.getStartTime());
|
||||
|
||||
Assert.assertEquals(end, chapter.getEnd());
|
||||
Assert.assertEquals(endTime, chapter.getEndTime());
|
||||
|
||||
Assert.assertNotNull(chapter.getTags());
|
||||
Assert.assertEquals(1, chapter.getTags().length());
|
||||
}
|
||||
|
||||
}
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* Copyright (c) 2021 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FFmpegSessionTest {
|
||||
|
||||
static final String[] TEST_ARGUMENTS = new String[]{"argument1", "argument2"};
|
||||
|
||||
@Test
|
||||
public void constructorTest() {
|
||||
FFmpegSession ffmpegSession = FFmpegSession.create(TEST_ARGUMENTS);
|
||||
|
||||
// 1. getCompleteCallback
|
||||
Assert.assertNull(ffmpegSession.getCompleteCallback());
|
||||
|
||||
// 2. getLogCallback
|
||||
Assert.assertNull(ffmpegSession.getLogCallback());
|
||||
|
||||
// 3. getStatisticsCallback
|
||||
Assert.assertNull(ffmpegSession.getStatisticsCallback());
|
||||
|
||||
// 4. getSessionId
|
||||
Assert.assertTrue(ffmpegSession.getSessionId() > 0);
|
||||
|
||||
// 5. getCreateTime
|
||||
Assert.assertTrue(ffmpegSession.getCreateTime().getTime() <= System.currentTimeMillis());
|
||||
|
||||
// 6. getStartTime
|
||||
Assert.assertNull(ffmpegSession.getStartTime());
|
||||
|
||||
// 7. getEndTime
|
||||
Assert.assertNull(ffmpegSession.getEndTime());
|
||||
|
||||
// 8. getDuration
|
||||
Assert.assertEquals(0, ffmpegSession.getDuration());
|
||||
|
||||
// 9. getArguments
|
||||
Assert.assertArrayEquals(TEST_ARGUMENTS, ffmpegSession.getArguments());
|
||||
|
||||
// 10. getCommand
|
||||
StringBuilder commandBuilder = new StringBuilder();
|
||||
for (int i = 0; i < TEST_ARGUMENTS.length; i++) {
|
||||
if (i > 0) {
|
||||
commandBuilder.append(" ");
|
||||
}
|
||||
commandBuilder.append(TEST_ARGUMENTS[i]);
|
||||
}
|
||||
Assert.assertEquals(commandBuilder.toString(), ffmpegSession.getCommand());
|
||||
|
||||
// 11. getLogs
|
||||
Assert.assertEquals(0, ffmpegSession.getLogs().size());
|
||||
|
||||
// 12. getLogsAsString
|
||||
Assert.assertEquals("", ffmpegSession.getLogsAsString());
|
||||
|
||||
// 13. getState
|
||||
Assert.assertEquals(SessionState.CREATED, ffmpegSession.getState());
|
||||
|
||||
// 14. getState
|
||||
Assert.assertNull(ffmpegSession.getReturnCode());
|
||||
|
||||
// 15. getFailStackTrace
|
||||
Assert.assertNull(ffmpegSession.getFailStackTrace());
|
||||
|
||||
// 16. getLogRedirectionStrategy
|
||||
Assert.assertEquals(FFmpegKitConfig.getLogRedirectionStrategy(), ffmpegSession.getLogRedirectionStrategy());
|
||||
|
||||
// 17. getFuture
|
||||
Assert.assertNull(ffmpegSession.getFuture());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorTest2() {
|
||||
FFmpegSessionCompleteCallback completeCallback = new FFmpegSessionCompleteCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(FFmpegSession session) {
|
||||
}
|
||||
};
|
||||
|
||||
FFmpegSession ffmpegSession = FFmpegSession.create(TEST_ARGUMENTS, completeCallback);
|
||||
|
||||
// 1. getCompleteCallback
|
||||
Assert.assertEquals(ffmpegSession.getCompleteCallback(), completeCallback);
|
||||
|
||||
// 2. getLogCallback
|
||||
Assert.assertNull(ffmpegSession.getLogCallback());
|
||||
|
||||
// 3. getStatisticsCallback
|
||||
Assert.assertNull(ffmpegSession.getStatisticsCallback());
|
||||
|
||||
// 4. getSessionId
|
||||
Assert.assertTrue(ffmpegSession.getSessionId() > 0);
|
||||
|
||||
// 5. getCreateTime
|
||||
Assert.assertTrue(ffmpegSession.getCreateTime().getTime() <= System.currentTimeMillis());
|
||||
|
||||
// 6. getStartTime
|
||||
Assert.assertNull(ffmpegSession.getStartTime());
|
||||
|
||||
// 7. getEndTime
|
||||
Assert.assertNull(ffmpegSession.getEndTime());
|
||||
|
||||
// 8. getDuration
|
||||
Assert.assertEquals(0, ffmpegSession.getDuration());
|
||||
|
||||
// 9. getArguments
|
||||
Assert.assertArrayEquals(TEST_ARGUMENTS, ffmpegSession.getArguments());
|
||||
|
||||
// 10. getCommand
|
||||
StringBuilder commandBuilder = new StringBuilder();
|
||||
for (int i = 0; i < TEST_ARGUMENTS.length; i++) {
|
||||
if (i > 0) {
|
||||
commandBuilder.append(" ");
|
||||
}
|
||||
commandBuilder.append(TEST_ARGUMENTS[i]);
|
||||
}
|
||||
Assert.assertEquals(commandBuilder.toString(), ffmpegSession.getCommand());
|
||||
|
||||
// 11. getLogs
|
||||
Assert.assertEquals(0, ffmpegSession.getLogs().size());
|
||||
|
||||
// 12. getLogsAsString
|
||||
Assert.assertEquals("", ffmpegSession.getLogsAsString());
|
||||
|
||||
// 13. getState
|
||||
Assert.assertEquals(SessionState.CREATED, ffmpegSession.getState());
|
||||
|
||||
// 14. getState
|
||||
Assert.assertNull(ffmpegSession.getReturnCode());
|
||||
|
||||
// 15. getFailStackTrace
|
||||
Assert.assertNull(ffmpegSession.getFailStackTrace());
|
||||
|
||||
// 16. getLogRedirectionStrategy
|
||||
Assert.assertEquals(FFmpegKitConfig.getLogRedirectionStrategy(), ffmpegSession.getLogRedirectionStrategy());
|
||||
|
||||
// 17. getFuture
|
||||
Assert.assertNull(ffmpegSession.getFuture());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorTest3() {
|
||||
FFmpegSessionCompleteCallback completeCallback = new FFmpegSessionCompleteCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(FFmpegSession session) {
|
||||
}
|
||||
};
|
||||
|
||||
LogCallback logCallback = new LogCallback() {
|
||||
@Override
|
||||
public void apply(Log log) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
StatisticsCallback statisticsCallback = new StatisticsCallback() {
|
||||
@Override
|
||||
public void apply(Statistics statistics) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
FFmpegSession ffmpegSession = FFmpegSession.create(TEST_ARGUMENTS, completeCallback, logCallback, statisticsCallback);
|
||||
|
||||
// 1. getCompleteCallback
|
||||
Assert.assertEquals(ffmpegSession.getCompleteCallback(), completeCallback);
|
||||
|
||||
// 2. getLogCallback
|
||||
Assert.assertEquals(ffmpegSession.getLogCallback(), logCallback);
|
||||
|
||||
// 3. getStatisticsCallback
|
||||
Assert.assertEquals(ffmpegSession.getStatisticsCallback(), statisticsCallback);
|
||||
|
||||
// 4. getSessionId
|
||||
Assert.assertTrue(ffmpegSession.getSessionId() > 0);
|
||||
|
||||
// 5. getCreateTime
|
||||
Assert.assertTrue(ffmpegSession.getCreateTime().getTime() <= System.currentTimeMillis());
|
||||
|
||||
// 6. getStartTime
|
||||
Assert.assertNull(ffmpegSession.getStartTime());
|
||||
|
||||
// 7. getEndTime
|
||||
Assert.assertNull(ffmpegSession.getEndTime());
|
||||
|
||||
// 8. getDuration
|
||||
Assert.assertEquals(0, ffmpegSession.getDuration());
|
||||
|
||||
// 9. getArguments
|
||||
Assert.assertArrayEquals(TEST_ARGUMENTS, ffmpegSession.getArguments());
|
||||
|
||||
// 10. getCommand
|
||||
StringBuilder commandBuilder = new StringBuilder();
|
||||
for (int i = 0; i < TEST_ARGUMENTS.length; i++) {
|
||||
if (i > 0) {
|
||||
commandBuilder.append(" ");
|
||||
}
|
||||
commandBuilder.append(TEST_ARGUMENTS[i]);
|
||||
}
|
||||
Assert.assertEquals(commandBuilder.toString(), ffmpegSession.getCommand());
|
||||
|
||||
// 11. getLogs
|
||||
Assert.assertEquals(0, ffmpegSession.getLogs().size());
|
||||
|
||||
// 12. getLogsAsString
|
||||
Assert.assertEquals("", ffmpegSession.getLogsAsString());
|
||||
|
||||
// 13. getState
|
||||
Assert.assertEquals(SessionState.CREATED, ffmpegSession.getState());
|
||||
|
||||
// 14. getState
|
||||
Assert.assertNull(ffmpegSession.getReturnCode());
|
||||
|
||||
// 15. getFailStackTrace
|
||||
Assert.assertNull(ffmpegSession.getFailStackTrace());
|
||||
|
||||
// 16. getLogRedirectionStrategy
|
||||
Assert.assertEquals(FFmpegKitConfig.getLogRedirectionStrategy(), ffmpegSession.getLogRedirectionStrategy());
|
||||
|
||||
// 17. getFuture
|
||||
Assert.assertNull(ffmpegSession.getFuture());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionIdTest() {
|
||||
FFmpegSession ffmpegSession1 = FFmpegSession.create(TEST_ARGUMENTS);
|
||||
FFmpegSession ffmpegSession2 = FFmpegSession.create(TEST_ARGUMENTS);
|
||||
FFmpegSession ffmpegSession3 = FFmpegSession.create(TEST_ARGUMENTS);
|
||||
|
||||
Assert.assertTrue(ffmpegSession3.getSessionId() > ffmpegSession2.getSessionId());
|
||||
Assert.assertTrue(ffmpegSession3.getSessionId() > ffmpegSession1.getSessionId());
|
||||
Assert.assertTrue(ffmpegSession2.getSessionId() > ffmpegSession1.getSessionId());
|
||||
|
||||
Assert.assertTrue(ffmpegSession1.getSessionId() > 0);
|
||||
Assert.assertTrue(ffmpegSession2.getSessionId() > 0);
|
||||
Assert.assertTrue(ffmpegSession3.getSessionId() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLogs() {
|
||||
final FFmpegSession ffmpegSession = FFmpegSession.create(TEST_ARGUMENTS);
|
||||
|
||||
String logMessage1 = "i am log one";
|
||||
String logMessage2 = "i am log two";
|
||||
String logMessage3 = "i am log three";
|
||||
|
||||
ffmpegSession.addLog(new Log(ffmpegSession.getSessionId(), Level.AV_LOG_INFO, logMessage1));
|
||||
ffmpegSession.addLog(new Log(ffmpegSession.getSessionId(), Level.AV_LOG_DEBUG, logMessage2));
|
||||
ffmpegSession.addLog(new Log(ffmpegSession.getSessionId(), Level.AV_LOG_TRACE, logMessage3));
|
||||
|
||||
List<Log> logs = ffmpegSession.getLogs();
|
||||
|
||||
Assert.assertEquals(3, logs.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLogsAsStringTest() {
|
||||
final FFmpegSession ffmpegSession = FFmpegSession.create(TEST_ARGUMENTS);
|
||||
|
||||
String logMessage1 = "i am log one";
|
||||
String logMessage2 = "i am log two";
|
||||
|
||||
ffmpegSession.addLog(new Log(ffmpegSession.getSessionId(), Level.AV_LOG_DEBUG, logMessage1));
|
||||
ffmpegSession.addLog(new Log(ffmpegSession.getSessionId(), Level.AV_LOG_DEBUG, logMessage2));
|
||||
|
||||
String logsAsString = ffmpegSession.getLogsAsString();
|
||||
|
||||
Assert.assertEquals(logMessage1 + logMessage2, logsAsString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLogRedirectionStrategy() {
|
||||
FFmpegKitConfig.setLogRedirectionStrategy(LogRedirectionStrategy.NEVER_PRINT_LOGS);
|
||||
|
||||
final FFmpegSession ffmpegSession1 = FFmpegSession.create(TEST_ARGUMENTS);
|
||||
Assert.assertEquals(FFmpegKitConfig.getLogRedirectionStrategy(), ffmpegSession1.getLogRedirectionStrategy());
|
||||
|
||||
FFmpegKitConfig.setLogRedirectionStrategy(LogRedirectionStrategy.PRINT_LOGS_WHEN_SESSION_CALLBACK_NOT_DEFINED);
|
||||
|
||||
final FFmpegSession ffmpegSession2 = FFmpegSession.create(TEST_ARGUMENTS);
|
||||
Assert.assertEquals(FFmpegKitConfig.getLogRedirectionStrategy(), ffmpegSession2.getLogRedirectionStrategy());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startRunningTest() {
|
||||
FFmpegSession ffmpegSession = FFmpegSession.create(TEST_ARGUMENTS);
|
||||
|
||||
ffmpegSession.startRunning();
|
||||
|
||||
Assert.assertEquals(SessionState.RUNNING, ffmpegSession.getState());
|
||||
Assert.assertTrue(ffmpegSession.getStartTime().getTime() <= System.currentTimeMillis());
|
||||
Assert.assertTrue(ffmpegSession.getCreateTime().getTime() <= ffmpegSession.getStartTime().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completeTest() {
|
||||
FFmpegSession ffmpegSession = FFmpegSession.create(TEST_ARGUMENTS);
|
||||
|
||||
ffmpegSession.startRunning();
|
||||
ffmpegSession.complete(new ReturnCode(100));
|
||||
|
||||
Assert.assertEquals(SessionState.COMPLETED, ffmpegSession.getState());
|
||||
Assert.assertEquals(100, ffmpegSession.getReturnCode().getValue());
|
||||
Assert.assertTrue(ffmpegSession.getStartTime().getTime() <= ffmpegSession.getEndTime().getTime());
|
||||
Assert.assertTrue(ffmpegSession.getDuration() >= 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failTest() {
|
||||
FFmpegSession ffmpegSession = FFmpegSession.create(TEST_ARGUMENTS);
|
||||
|
||||
ffmpegSession.startRunning();
|
||||
ffmpegSession.fail(new Exception(""));
|
||||
|
||||
Assert.assertEquals(SessionState.FAILED, ffmpegSession.getState());
|
||||
Assert.assertNull(ffmpegSession.getReturnCode());
|
||||
Assert.assertTrue(ffmpegSession.getStartTime().getTime() <= ffmpegSession.getEndTime().getTime());
|
||||
Assert.assertTrue(ffmpegSession.getDuration() >= 0);
|
||||
Assert.assertNotNull(ffmpegSession.getFailStackTrace());
|
||||
}
|
||||
|
||||
}
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* Copyright (c) 2021 Taner Sener
|
||||
*
|
||||
* This file is part of FFmpegKit.
|
||||
*
|
||||
* FFmpegKit is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FFmpegKit is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FFprobeSessionTest {
|
||||
|
||||
private static final String[] TEST_ARGUMENTS = new String[]{"argument1", "argument2"};
|
||||
|
||||
@Test
|
||||
public void constructorTest() {
|
||||
FFprobeSession ffprobeSession = FFprobeSession.create(TEST_ARGUMENTS);
|
||||
|
||||
// 1. getCompleteCallback
|
||||
Assert.assertNull(ffprobeSession.getCompleteCallback());
|
||||
|
||||
// 2. getLogCallback
|
||||
Assert.assertNull(ffprobeSession.getLogCallback());
|
||||
|
||||
// 3. getSessionId
|
||||
Assert.assertTrue(ffprobeSession.getSessionId() > 0);
|
||||
|
||||
// 4. getCreateTime
|
||||
Assert.assertTrue(ffprobeSession.getCreateTime().getTime() <= System.currentTimeMillis());
|
||||
|
||||
// 5. getStartTime
|
||||
Assert.assertNull(ffprobeSession.getStartTime());
|
||||
|
||||
// 6. getEndTime
|
||||
Assert.assertNull(ffprobeSession.getEndTime());
|
||||
|
||||
// 7. getDuration
|
||||
Assert.assertEquals(0, ffprobeSession.getDuration());
|
||||
|
||||
// 8. getArguments
|
||||
Assert.assertArrayEquals(TEST_ARGUMENTS, ffprobeSession.getArguments());
|
||||
|
||||
// 9. getCommand
|
||||
StringBuilder commandBuilder = new StringBuilder();
|
||||
for (int i = 0; i < TEST_ARGUMENTS.length; i++) {
|
||||
if (i > 0) {
|
||||
commandBuilder.append(" ");
|
||||
}
|
||||
commandBuilder.append(TEST_ARGUMENTS[i]);
|
||||
}
|
||||
Assert.assertEquals(commandBuilder.toString(), ffprobeSession.getCommand());
|
||||
|
||||
// 10. getLogs
|
||||
Assert.assertEquals(0, ffprobeSession.getLogs().size());
|
||||
|
||||
// 11. getLogsAsString
|
||||
Assert.assertEquals("", ffprobeSession.getLogsAsString());
|
||||
|
||||
// 12. getState
|
||||
Assert.assertEquals(SessionState.CREATED, ffprobeSession.getState());
|
||||
|
||||
// 13. getState
|
||||
Assert.assertNull(ffprobeSession.getReturnCode());
|
||||
|
||||
// 14. getFailStackTrace
|
||||
Assert.assertNull(ffprobeSession.getFailStackTrace());
|
||||
|
||||
// 15. getLogRedirectionStrategy
|
||||
Assert.assertEquals(FFmpegKitConfig.getLogRedirectionStrategy(), ffprobeSession.getLogRedirectionStrategy());
|
||||
|
||||
// 16. getFuture
|
||||
Assert.assertNull(ffprobeSession.getFuture());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorTest2() {
|
||||
FFprobeSessionCompleteCallback completeCallback = new FFprobeSessionCompleteCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(FFprobeSession session) {
|
||||
}
|
||||
};
|
||||
|
||||
FFprobeSession ffprobeSession = FFprobeSession.create(TEST_ARGUMENTS, completeCallback);
|
||||
|
||||
// 1. getCompleteCallback
|
||||
Assert.assertEquals(ffprobeSession.getCompleteCallback(), completeCallback);
|
||||
|
||||
// 2. getLogCallback
|
||||
Assert.assertNull(ffprobeSession.getLogCallback());
|
||||
|
||||
// 3. getSessionId
|
||||
Assert.assertTrue(ffprobeSession.getSessionId() > 0);
|
||||
|
||||
// 4. getCreateTime
|
||||
Assert.assertTrue(ffprobeSession.getCreateTime().getTime() <= System.currentTimeMillis());
|
||||
|
||||
// 5. getStartTime
|
||||
Assert.assertNull(ffprobeSession.getStartTime());
|
||||
|
||||
// 6. getEndTime
|
||||
Assert.assertNull(ffprobeSession.getEndTime());
|
||||
|
||||
// 7. getDuration
|
||||
Assert.assertEquals(0, ffprobeSession.getDuration());
|
||||
|
||||
// 8. getArguments
|
||||
Assert.assertArrayEquals(TEST_ARGUMENTS, ffprobeSession.getArguments());
|
||||
|
||||
// 9. getCommand
|
||||
StringBuilder commandBuilder = new StringBuilder();
|
||||
for (int i = 0; i < TEST_ARGUMENTS.length; i++) {
|
||||
if (i > 0) {
|
||||
commandBuilder.append(" ");
|
||||
}
|
||||
commandBuilder.append(TEST_ARGUMENTS[i]);
|
||||
}
|
||||
Assert.assertEquals(commandBuilder.toString(), ffprobeSession.getCommand());
|
||||
|
||||
// 10. getLogs
|
||||
Assert.assertEquals(0, ffprobeSession.getLogs().size());
|
||||
|
||||
// 11. getLogsAsString
|
||||
Assert.assertEquals("", ffprobeSession.getLogsAsString());
|
||||
|
||||
// 12. getState
|
||||
Assert.assertEquals(SessionState.CREATED, ffprobeSession.getState());
|
||||
|
||||
// 13. getState
|
||||
Assert.assertNull(ffprobeSession.getReturnCode());
|
||||
|
||||
// 14. getFailStackTrace
|
||||
Assert.assertNull(ffprobeSession.getFailStackTrace());
|
||||
|
||||
// 15. getLogRedirectionStrategy
|
||||
Assert.assertEquals(FFmpegKitConfig.getLogRedirectionStrategy(), ffprobeSession.getLogRedirectionStrategy());
|
||||
|
||||
// 16. getFuture
|
||||
Assert.assertNull(ffprobeSession.getFuture());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorTest3() {
|
||||
FFprobeSessionCompleteCallback completeCallback = new FFprobeSessionCompleteCallback() {
|
||||
|
||||
@Override
|
||||
public void apply(FFprobeSession session) {
|
||||
}
|
||||
};
|
||||
|
||||
LogCallback logCallback = new LogCallback() {
|
||||
@Override
|
||||
public void apply(Log log) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
FFprobeSession ffprobeSession = FFprobeSession.create(TEST_ARGUMENTS, completeCallback, logCallback);
|
||||
|
||||
// 1. getCompleteCallback
|
||||
Assert.assertEquals(ffprobeSession.getCompleteCallback(), completeCallback);
|
||||
|
||||
// 2. getLogCallback
|
||||
Assert.assertEquals(ffprobeSession.getLogCallback(), logCallback);
|
||||
|
||||
// 3. getSessionId
|
||||
Assert.assertTrue(ffprobeSession.getSessionId() > 0);
|
||||
|
||||
// 4. getCreateTime
|
||||
Assert.assertTrue(ffprobeSession.getCreateTime().getTime() <= System.currentTimeMillis());
|
||||
|
||||
// 5. getStartTime
|
||||
Assert.assertNull(ffprobeSession.getStartTime());
|
||||
|
||||
// 6. getEndTime
|
||||
Assert.assertNull(ffprobeSession.getEndTime());
|
||||
|
||||
// 7. getDuration
|
||||
Assert.assertEquals(0, ffprobeSession.getDuration());
|
||||
|
||||
// 8. getArguments
|
||||
Assert.assertArrayEquals(TEST_ARGUMENTS, ffprobeSession.getArguments());
|
||||
|
||||
// 9. getCommand
|
||||
StringBuilder commandBuilder = new StringBuilder();
|
||||
for (int i = 0; i < TEST_ARGUMENTS.length; i++) {
|
||||
if (i > 0) {
|
||||
commandBuilder.append(" ");
|
||||
}
|
||||
commandBuilder.append(TEST_ARGUMENTS[i]);
|
||||
}
|
||||
Assert.assertEquals(commandBuilder.toString(), ffprobeSession.getCommand());
|
||||
|
||||
// 10. getLogs
|
||||
Assert.assertEquals(0, ffprobeSession.getLogs().size());
|
||||
|
||||
// 11. getLogsAsString
|
||||
Assert.assertEquals("", ffprobeSession.getLogsAsString());
|
||||
|
||||
// 12. getState
|
||||
Assert.assertEquals(SessionState.CREATED, ffprobeSession.getState());
|
||||
|
||||
// 13. getState
|
||||
Assert.assertNull(ffprobeSession.getReturnCode());
|
||||
|
||||
// 14. getFailStackTrace
|
||||
Assert.assertNull(ffprobeSession.getFailStackTrace());
|
||||
|
||||
// 15. getLogRedirectionStrategy
|
||||
Assert.assertEquals(FFmpegKitConfig.getLogRedirectionStrategy(), ffprobeSession.getLogRedirectionStrategy());
|
||||
|
||||
// 16. getFuture
|
||||
Assert.assertNull(ffprobeSession.getFuture());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionIdTest() {
|
||||
FFprobeSession ffprobeSession1 = FFprobeSession.create(TEST_ARGUMENTS);
|
||||
FFprobeSession ffprobeSession2 = FFprobeSession.create(TEST_ARGUMENTS);
|
||||
FFprobeSession ffprobeSession3 = FFprobeSession.create(TEST_ARGUMENTS);
|
||||
|
||||
Assert.assertTrue(ffprobeSession3.getSessionId() > ffprobeSession2.getSessionId());
|
||||
Assert.assertTrue(ffprobeSession3.getSessionId() > ffprobeSession1.getSessionId());
|
||||
Assert.assertTrue(ffprobeSession2.getSessionId() > ffprobeSession1.getSessionId());
|
||||
|
||||
Assert.assertTrue(ffprobeSession1.getSessionId() > 0);
|
||||
Assert.assertTrue(ffprobeSession2.getSessionId() > 0);
|
||||
Assert.assertTrue(ffprobeSession3.getSessionId() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLogs() {
|
||||
final FFprobeSession ffprobeSession = FFprobeSession.create(TEST_ARGUMENTS);
|
||||
|
||||
String logMessage1 = "i am log one";
|
||||
String logMessage2 = "i am log two";
|
||||
String logMessage3 = "i am log three";
|
||||
|
||||
ffprobeSession.addLog(new Log(ffprobeSession.getSessionId(), Level.AV_LOG_INFO, logMessage1));
|
||||
ffprobeSession.addLog(new Log(ffprobeSession.getSessionId(), Level.AV_LOG_DEBUG, logMessage2));
|
||||
ffprobeSession.addLog(new Log(ffprobeSession.getSessionId(), Level.AV_LOG_TRACE, logMessage3));
|
||||
|
||||
List<Log> logs = ffprobeSession.getLogs();
|
||||
|
||||
Assert.assertEquals(3, logs.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLogsAsStringTest() {
|
||||
final FFprobeSession ffprobeSession = FFprobeSession.create(TEST_ARGUMENTS);
|
||||
|
||||
String logMessage1 = "i am log one";
|
||||
String logMessage2 = "i am log two";
|
||||
|
||||
ffprobeSession.addLog(new Log(ffprobeSession.getSessionId(), Level.AV_LOG_DEBUG, logMessage1));
|
||||
ffprobeSession.addLog(new Log(ffprobeSession.getSessionId(), Level.AV_LOG_DEBUG, logMessage2));
|
||||
|
||||
String logsAsString = ffprobeSession.getLogsAsString();
|
||||
|
||||
Assert.assertEquals(logMessage1 + logMessage2, logsAsString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLogRedirectionStrategy() {
|
||||
FFmpegKitConfig.setLogRedirectionStrategy(LogRedirectionStrategy.NEVER_PRINT_LOGS);
|
||||
|
||||
final FFprobeSession ffprobeSession1 = FFprobeSession.create(TEST_ARGUMENTS);
|
||||
Assert.assertEquals(FFmpegKitConfig.getLogRedirectionStrategy(), ffprobeSession1.getLogRedirectionStrategy());
|
||||
|
||||
FFmpegKitConfig.setLogRedirectionStrategy(LogRedirectionStrategy.PRINT_LOGS_WHEN_SESSION_CALLBACK_NOT_DEFINED);
|
||||
|
||||
final FFprobeSession ffprobeSession2 = FFprobeSession.create(TEST_ARGUMENTS);
|
||||
Assert.assertEquals(FFmpegKitConfig.getLogRedirectionStrategy(), ffprobeSession2.getLogRedirectionStrategy());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startRunningTest() {
|
||||
FFprobeSession ffprobeSession = FFprobeSession.create(TEST_ARGUMENTS);
|
||||
|
||||
ffprobeSession.startRunning();
|
||||
|
||||
Assert.assertEquals(SessionState.RUNNING, ffprobeSession.getState());
|
||||
Assert.assertTrue(ffprobeSession.getStartTime().getTime() <= System.currentTimeMillis());
|
||||
Assert.assertTrue(ffprobeSession.getCreateTime().getTime() <= ffprobeSession.getStartTime().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completeTest() {
|
||||
FFprobeSession ffprobeSession = FFprobeSession.create(TEST_ARGUMENTS);
|
||||
|
||||
ffprobeSession.startRunning();
|
||||
ffprobeSession.complete(new ReturnCode(100));
|
||||
|
||||
Assert.assertEquals(SessionState.COMPLETED, ffprobeSession.getState());
|
||||
Assert.assertEquals(100, ffprobeSession.getReturnCode().getValue());
|
||||
Assert.assertTrue(ffprobeSession.getStartTime().getTime() <= ffprobeSession.getEndTime().getTime());
|
||||
Assert.assertTrue(ffprobeSession.getDuration() >= 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failTest() {
|
||||
FFprobeSession ffprobeSession = FFprobeSession.create(TEST_ARGUMENTS);
|
||||
|
||||
ffprobeSession.startRunning();
|
||||
ffprobeSession.fail(new Exception(""));
|
||||
|
||||
Assert.assertEquals(SessionState.FAILED, ffprobeSession.getState());
|
||||
Assert.assertNull(ffprobeSession.getReturnCode());
|
||||
Assert.assertTrue(ffprobeSession.getStartTime().getTime() <= ffprobeSession.getEndTime().getTime());
|
||||
Assert.assertTrue(ffprobeSession.getDuration() >= 0);
|
||||
Assert.assertNotNull(ffprobeSession.getFailStackTrace());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user