You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1407 lines
45 KiB
1407 lines
45 KiB
/*
|
|
* 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/>.
|
|
*/
|
|
|
|
#import <stdatomic.h>
|
|
#import <sys/types.h>
|
|
#import <sys/stat.h>
|
|
#import "libavutil/ffversion.h"
|
|
#import "libavutil/bprint.h"
|
|
#import "fftools_ffmpeg.h"
|
|
#import "ArchDetect.h"
|
|
#import "AtomicLong.h"
|
|
#import "FFmpegKit.h"
|
|
#import "FFmpegKitConfig.h"
|
|
#import "FFmpegSession.h"
|
|
#import "FFprobeKit.h"
|
|
#import "FFprobeSession.h"
|
|
#import "Level.h"
|
|
#import "LogRedirectionStrategy.h"
|
|
#import "MediaInformationSession.h"
|
|
#import "SessionState.h"
|
|
|
|
/** Global library version */
|
|
NSString* const FFmpegKitVersion = @"6.0";
|
|
|
|
/**
|
|
* Prefix of named pipes created by ffmpeg-kit.
|
|
*/
|
|
NSString* const FFmpegKitNamedPipePrefix = @"fk_pipe_";
|
|
|
|
/**
|
|
* Generates ids for named ffmpeg kit pipes.
|
|
*/
|
|
static AtomicLong* pipeIndexGenerator;
|
|
|
|
/* Session history variables */
|
|
static int sessionHistorySize;
|
|
static volatile NSMutableDictionary* sessionHistoryMap;
|
|
static NSMutableArray* sessionHistoryList;
|
|
static NSRecursiveLock* sessionHistoryLock;
|
|
|
|
/** Session control variables */
|
|
#define SESSION_MAP_SIZE 1000
|
|
static atomic_short sessionMap[SESSION_MAP_SIZE];
|
|
static atomic_int sessionInTransitMessageCountMap[SESSION_MAP_SIZE];
|
|
|
|
static dispatch_queue_t asyncDispatchQueue;
|
|
|
|
/** Holds callback defined to redirect logs */
|
|
static LogCallback logCallback;
|
|
|
|
/** Holds callback defined to redirect statistics */
|
|
static StatisticsCallback statisticsCallback;
|
|
|
|
/** Holds complete callbacks defined to redirect asynchronous execution results */
|
|
static FFmpegSessionCompleteCallback ffmpegSessionCompleteCallback;
|
|
static FFprobeSessionCompleteCallback ffprobeSessionCompleteCallback;
|
|
static MediaInformationSessionCompleteCallback mediaInformationSessionCompleteCallback;
|
|
|
|
static LogRedirectionStrategy globalLogRedirectionStrategy;
|
|
|
|
/** Redirection control variables */
|
|
static int redirectionEnabled;
|
|
static NSRecursiveLock *lock;
|
|
static dispatch_semaphore_t semaphore;
|
|
static NSMutableArray *callbackDataArray;
|
|
|
|
/** 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 execution */
|
|
__thread long globalSessionId = 0;
|
|
|
|
/** Holds the default log level */
|
|
int configuredLogLevel = LevelAVLogInfo;
|
|
|
|
/** Forward declaration for function defined in fftools_ffmpeg.c */
|
|
int ffmpeg_execute(int argc, char **argv);
|
|
|
|
/** Forward declaration for function defined in fftools_ffprobe.c */
|
|
int ffprobe_execute(int argc, char **argv);
|
|
|
|
typedef NS_ENUM(NSUInteger, CallbackType) {
|
|
LogType,
|
|
StatisticsType
|
|
};
|
|
|
|
void deleteExpiredSessions() {
|
|
while ([sessionHistoryList count] > sessionHistorySize) {
|
|
id<Session> first = [sessionHistoryList firstObject];
|
|
if (first != nil) {
|
|
[sessionHistoryList removeObjectAtIndex:0];
|
|
[sessionHistoryMap removeObjectForKey:[NSNumber numberWithLong:[first getSessionId]]];
|
|
}
|
|
}
|
|
}
|
|
|
|
void addSessionToSessionHistory(id<Session> session) {
|
|
NSNumber* sessionIdNumber = [NSNumber numberWithLong:[session getSessionId]];
|
|
|
|
[sessionHistoryLock lock];
|
|
|
|
/*
|
|
* ASYNC SESSIONS CALL THIS METHOD TWICE
|
|
* THIS CHECK PREVENTS ADDING THE SAME SESSION AGAIN
|
|
*/
|
|
if ([sessionHistoryMap objectForKey:sessionIdNumber] == nil) {
|
|
[sessionHistoryMap setObject:session forKey:sessionIdNumber];
|
|
[sessionHistoryList addObject:session];
|
|
deleteExpiredSessions();
|
|
}
|
|
|
|
[sessionHistoryLock unlock];
|
|
}
|
|
|
|
/**
|
|
* Callback data class.
|
|
*/
|
|
@interface CallbackData : NSObject
|
|
|
|
@end
|
|
|
|
@implementation CallbackData {
|
|
CallbackType _type;
|
|
long _sessionId; // session id
|
|
|
|
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
|
|
}
|
|
|
|
- (instancetype)init:(long)sessionId logLevel:(int)logLevel data:(AVBPrint*)data {
|
|
self = [super init];
|
|
if (self) {
|
|
_type = LogType;
|
|
_sessionId = sessionId;
|
|
_logLevel = logLevel;
|
|
av_bprint_init(&_logData, 0, AV_BPRINT_SIZE_UNLIMITED);
|
|
av_bprintf(&_logData, "%s", data->str);
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (instancetype)init:(long)sessionId
|
|
videoFrameNumber:(int)videoFrameNumber
|
|
fps:(float)videoFps
|
|
quality:(float)videoQuality
|
|
size:(int64_t)size
|
|
time:(double)time
|
|
bitrate:(double)bitrate
|
|
speed:(double)speed {
|
|
self = [super init];
|
|
if (self) {
|
|
_type = StatisticsType;
|
|
_sessionId = sessionId;
|
|
_statisticsFrameNumber = videoFrameNumber;
|
|
_statisticsFps = videoFps;
|
|
_statisticsQuality = videoQuality;
|
|
_statisticsSize = size;
|
|
_statisticsTime = time;
|
|
_statisticsBitrate = bitrate;
|
|
_statisticsSpeed = speed;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (CallbackType)getType {
|
|
return _type;
|
|
}
|
|
|
|
- (long)getSessionId {
|
|
return _sessionId;
|
|
}
|
|
|
|
- (int)getLogLevel {
|
|
return _logLevel;
|
|
}
|
|
|
|
- (AVBPrint*)getLogData {
|
|
return &_logData;
|
|
}
|
|
|
|
- (int)getStatisticsFrameNumber {
|
|
return _statisticsFrameNumber;
|
|
}
|
|
|
|
- (float)getStatisticsFps {
|
|
return _statisticsFps;
|
|
}
|
|
|
|
- (float)getStatisticsQuality {
|
|
return _statisticsQuality;
|
|
}
|
|
|
|
- (int64_t)getStatisticsSize {
|
|
return _statisticsSize;
|
|
}
|
|
|
|
- (double)getStatisticsTime {
|
|
return _statisticsTime;
|
|
}
|
|
|
|
- (double)getStatisticsBitrate {
|
|
return _statisticsBitrate;
|
|
}
|
|
|
|
- (double)getStatisticsSpeed {
|
|
return _statisticsSpeed;
|
|
}
|
|
|
|
@end
|
|
|
|
/**
|
|
* Waits on the callback semaphore for the given time.
|
|
*
|
|
* @param milliSeconds wait time in milliseconds
|
|
*/
|
|
void callbackWait(int milliSeconds) {
|
|
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(milliSeconds * NSEC_PER_MSEC)));
|
|
}
|
|
|
|
/**
|
|
* Notifies threads waiting on callback semaphore.
|
|
*/
|
|
void callbackNotify() {
|
|
dispatch_semaphore_signal(semaphore);
|
|
}
|
|
|
|
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++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds log data to the end of callback data list.
|
|
*
|
|
* @param level log level
|
|
* @param data log data
|
|
*/
|
|
void logCallbackDataAdd(int level, AVBPrint *data) {
|
|
CallbackData* callbackData = [[CallbackData alloc] init:globalSessionId logLevel:level data:data];
|
|
|
|
[lock lock];
|
|
[callbackDataArray addObject:callbackData];
|
|
[lock unlock];
|
|
|
|
callbackNotify();
|
|
|
|
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) {
|
|
CallbackData *callbackData = [[CallbackData alloc] init:globalSessionId videoFrameNumber:frameNumber fps:fps quality:quality size:size time:time bitrate:bitrate speed:speed];
|
|
|
|
[lock lock];
|
|
[callbackDataArray addObject:callbackData];
|
|
[lock unlock];
|
|
|
|
callbackNotify();
|
|
|
|
atomic_fetch_add(&sessionInTransitMessageCountMap[globalSessionId % SESSION_MAP_SIZE], 1);
|
|
}
|
|
|
|
/**
|
|
* Removes head of callback data list.
|
|
*/
|
|
CallbackData *callbackDataRemove() {
|
|
CallbackData *newData = nil;
|
|
|
|
[lock lock];
|
|
|
|
@try {
|
|
if ([callbackDataArray count] > 0) {
|
|
newData = [callbackDataArray objectAtIndex:0];
|
|
[callbackDataArray removeObjectAtIndex:0];
|
|
}
|
|
} @catch(NSException *exception) {
|
|
// DO NOTHING
|
|
} @finally {
|
|
[lock unlock];
|
|
}
|
|
|
|
return newData;
|
|
}
|
|
|
|
/**
|
|
* Registers a session id to the session map.
|
|
*
|
|
* @param sessionId session id
|
|
*/
|
|
void registerSessionId(long sessionId) {
|
|
atomic_store(&sessionMap[sessionId % SESSION_MAP_SIZE], 1);
|
|
}
|
|
|
|
/**
|
|
* Removes a session id from the session map.
|
|
*
|
|
* @param sessionId session id
|
|
*/
|
|
void removeSession(long sessionId) {
|
|
atomic_store(&sessionMap[sessionId % SESSION_MAP_SIZE], 0);
|
|
}
|
|
|
|
/**
|
|
* Adds a cancel session request to the session map.
|
|
*
|
|
* @param sessionId session id
|
|
*/
|
|
void cancelSession(long sessionId) {
|
|
atomic_store(&sessionMap[sessionId % SESSION_MAP_SIZE], 2);
|
|
}
|
|
|
|
/**
|
|
* Checks whether a cancel request for the given session id exists in the session map.
|
|
*
|
|
* @param sessionId session id
|
|
* @return 1 if exists, false otherwise
|
|
*/
|
|
int cancelRequested(long sessionId) {
|
|
if (atomic_load(&sessionMap[sessionId % SESSION_MAP_SIZE]) == 2) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets the number of messages in transmit for this session.
|
|
*
|
|
* @param sessionId session id
|
|
*/
|
|
void resetMessagesInTransmit(long sessionId) {
|
|
atomic_store(&sessionInTransitMessageCountMap[sessionId % SESSION_MAP_SIZE], 0);
|
|
}
|
|
|
|
/**
|
|
* Callback function for FFmpeg/FFprobe 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;
|
|
|
|
// DO NOT PROCESS UNWANTED LOGS
|
|
if (level >= 0) {
|
|
level &= 0xff;
|
|
}
|
|
int activeLogLevel = av_log_get_level();
|
|
|
|
// LevelAVLogStdErr logs are always redirected
|
|
if ((activeLogLevel == LevelAVLogQuiet && level != LevelAVLogStdErr) || (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);
|
|
}
|
|
|
|
void process_log(long sessionId, int levelValue, AVBPrint* logMessage) {
|
|
int activeLogLevel = av_log_get_level();
|
|
NSString* message = [NSString stringWithCString:logMessage->str encoding:NSUTF8StringEncoding];
|
|
if (message == nil) {
|
|
// WE DROP LOGS THAT WE CANNOT DISPLAY
|
|
return;
|
|
}
|
|
Log* log = [[Log alloc] init:sessionId:levelValue:message];
|
|
BOOL globalCallbackDefined = false;
|
|
BOOL sessionCallbackDefined = false;
|
|
LogRedirectionStrategy activeLogRedirectionStrategy = globalLogRedirectionStrategy;
|
|
|
|
// LevelAVLogStdErr logs are always redirected
|
|
if ((activeLogLevel == LevelAVLogQuiet && levelValue != LevelAVLogStdErr) || (levelValue > activeLogLevel)) {
|
|
// LOG NEITHER PRINTED NOR FORWARDED
|
|
return;
|
|
}
|
|
|
|
id<Session> session = [FFmpegKitConfig getSession:sessionId];
|
|
if (session != nil) {
|
|
activeLogRedirectionStrategy = [session getLogRedirectionStrategy];
|
|
[session addLog:log];
|
|
|
|
LogCallback sessionLogCallback = [session getLogCallback];
|
|
if (sessionLogCallback != nil) {
|
|
sessionCallbackDefined = TRUE;
|
|
|
|
@try {
|
|
// NOTIFY SESSION CALLBACK DEFINED
|
|
sessionLogCallback(log);
|
|
}
|
|
@catch(NSException* exception) {
|
|
NSLog(@"Exception thrown inside session log callback. %@", [exception callStackSymbols]);
|
|
}
|
|
}
|
|
}
|
|
|
|
LogCallback globalLogCallback = logCallback;
|
|
if (globalLogCallback != nil) {
|
|
globalCallbackDefined = TRUE;
|
|
|
|
@try {
|
|
// NOTIFY GLOBAL CALLBACK DEFINED
|
|
globalLogCallback(log);
|
|
}
|
|
@catch(NSException* exception) {
|
|
NSLog(@"Exception thrown inside global log callback. %@", [exception callStackSymbols]);
|
|
}
|
|
}
|
|
|
|
// EXECUTE THE LOG STRATEGY
|
|
switch (activeLogRedirectionStrategy) {
|
|
case LogRedirectionStrategyNeverPrintLogs: {
|
|
return;
|
|
}
|
|
case LogRedirectionStrategyPrintLogsWhenGlobalCallbackNotDefined: {
|
|
if (globalCallbackDefined) {
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case LogRedirectionStrategyPrintLogsWhenSessionCallbackNotDefined: {
|
|
if (sessionCallbackDefined) {
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case LogRedirectionStrategyPrintLogsWhenNoCallbacksDefined: {
|
|
if (globalCallbackDefined || sessionCallbackDefined) {
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case LogRedirectionStrategyAlwaysPrintLogs: {
|
|
}
|
|
break;
|
|
}
|
|
|
|
// PRINT LOGS
|
|
switch (levelValue) {
|
|
case LevelAVLogQuiet:
|
|
// PRINT NO OUTPUT
|
|
break;
|
|
default:
|
|
// WRITE TO NSLOG
|
|
NSLog(@"%@: %@", [FFmpegKitConfig logLevelToString:levelValue], [NSString stringWithCString:logMessage->str encoding:NSUTF8StringEncoding]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void process_statistics(long sessionId, int videoFrameNumber, float videoFps, float videoQuality, long size, double time, double bitrate, double speed) {
|
|
|
|
Statistics *statistics = [[Statistics alloc] init:sessionId videoFrameNumber:videoFrameNumber videoFps:videoFps videoQuality:videoQuality size:size time:time bitrate:bitrate speed:speed];
|
|
|
|
id<Session> session = [FFmpegKitConfig getSession:sessionId];
|
|
if (session != nil && [session isFFmpeg]) {
|
|
FFmpegSession *ffmpegSession = (FFmpegSession*)session;
|
|
[ffmpegSession addStatistics:statistics];
|
|
|
|
StatisticsCallback sessionStatisticsCallback = [ffmpegSession getStatisticsCallback];
|
|
if (sessionStatisticsCallback != nil) {
|
|
@try {
|
|
sessionStatisticsCallback(statistics);
|
|
}
|
|
@catch(NSException* exception) {
|
|
NSLog(@"Exception thrown inside session statistics callback. %@", [exception callStackSymbols]);
|
|
}
|
|
}
|
|
}
|
|
|
|
StatisticsCallback globalStatisticsCallback = statisticsCallback;
|
|
if (globalStatisticsCallback != nil) {
|
|
@try {
|
|
globalStatisticsCallback(statistics);
|
|
}
|
|
@catch(NSException* exception) {
|
|
NSLog(@"Exception thrown inside global statistics callback. %@", [exception callStackSymbols]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Forwards asynchronous messages to Callbacks.
|
|
*/
|
|
void callbackBlockFunction() {
|
|
int activeLogLevel = av_log_get_level();
|
|
if ((activeLogLevel != LevelAVLogQuiet) && (LevelAVLogDebug <= activeLogLevel)) {
|
|
NSLog(@"Async callback block started.\n");
|
|
}
|
|
|
|
while(redirectionEnabled) {
|
|
@autoreleasepool {
|
|
@try {
|
|
|
|
CallbackData *callbackData = callbackDataRemove();
|
|
if (callbackData != nil) {
|
|
|
|
if ([callbackData getType] == LogType) {
|
|
process_log([callbackData getSessionId], [callbackData getLogLevel], [callbackData getLogData]);
|
|
av_bprint_finalize([callbackData getLogData], NULL);
|
|
} else {
|
|
process_statistics([callbackData getSessionId],
|
|
[callbackData getStatisticsFrameNumber],
|
|
[callbackData getStatisticsFps],
|
|
[callbackData getStatisticsQuality],
|
|
[callbackData getStatisticsSize],
|
|
[callbackData getStatisticsTime],
|
|
[callbackData getStatisticsBitrate],
|
|
[callbackData getStatisticsSpeed]);
|
|
}
|
|
|
|
atomic_fetch_sub(&sessionInTransitMessageCountMap[[callbackData getSessionId] % SESSION_MAP_SIZE], 1);
|
|
|
|
} else {
|
|
callbackWait(100);
|
|
}
|
|
|
|
} @catch(NSException *exception) {
|
|
activeLogLevel = av_log_get_level();
|
|
if ((activeLogLevel != LevelAVLogQuiet) && (LevelAVLogWarning <= activeLogLevel)) {
|
|
NSLog(@"Async callback block received error: %@n\n", exception);
|
|
NSLog(@"%@", [exception callStackSymbols]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
activeLogLevel = av_log_get_level();
|
|
if ((activeLogLevel != LevelAVLogQuiet) && (LevelAVLogDebug <= activeLogLevel)) {
|
|
NSLog(@"Async callback block stopped.\n");
|
|
}
|
|
}
|
|
|
|
int executeFFmpeg(long sessionId, NSArray* arguments) {
|
|
NSString* const LIB_NAME = @"ffmpeg";
|
|
|
|
// SETS DEFAULT LOG LEVEL BEFORE STARTING A NEW RUN
|
|
av_log_set_level(configuredLogLevel);
|
|
|
|
char **commandCharPArray = (char **)av_malloc(sizeof(char*) * ([arguments count] + 1));
|
|
|
|
/* PRESERVE USAGE FORMAT
|
|
*
|
|
* ffmpeg <arguments>
|
|
*/
|
|
commandCharPArray[0] = (char *)av_malloc(sizeof(char) * ([LIB_NAME length] + 1));
|
|
strcpy(commandCharPArray[0], [LIB_NAME UTF8String]);
|
|
|
|
// PREPARE ARRAY ELEMENTS
|
|
for (int i=0; i < [arguments count]; i++) {
|
|
NSString *argument = [arguments objectAtIndex:i];
|
|
commandCharPArray[i + 1] = (char *) [argument UTF8String];
|
|
}
|
|
|
|
// REGISTER THE ID BEFORE STARTING THE SESSION
|
|
globalSessionId = sessionId;
|
|
registerSessionId(sessionId);
|
|
|
|
resetMessagesInTransmit(sessionId);
|
|
|
|
// RUN
|
|
int returnCode = ffmpeg_execute(([arguments count] + 1), commandCharPArray);
|
|
|
|
// ALWAYS REMOVE THE ID FROM THE MAP
|
|
removeSession(sessionId);
|
|
|
|
// CLEANUP
|
|
av_free(commandCharPArray[0]);
|
|
av_free(commandCharPArray);
|
|
|
|
return returnCode;
|
|
}
|
|
|
|
int executeFFprobe(long sessionId, NSArray* arguments) {
|
|
NSString* const LIB_NAME = @"ffprobe";
|
|
|
|
// SETS DEFAULT LOG LEVEL BEFORE STARTING A NEW RUN
|
|
av_log_set_level(configuredLogLevel);
|
|
|
|
char **commandCharPArray = (char **)av_malloc(sizeof(char*) * ([arguments count] + 1));
|
|
|
|
/* PRESERVE USAGE FORMAT
|
|
*
|
|
* ffprobe <arguments>
|
|
*/
|
|
commandCharPArray[0] = (char *)av_malloc(sizeof(char) * ([LIB_NAME length] + 1));
|
|
strcpy(commandCharPArray[0], [LIB_NAME UTF8String]);
|
|
|
|
// PREPARE ARRAY ELEMENTS
|
|
for (int i=0; i < [arguments count]; i++) {
|
|
NSString *argument = [arguments objectAtIndex:i];
|
|
commandCharPArray[i + 1] = (char *) [argument UTF8String];
|
|
}
|
|
|
|
// REGISTER THE ID BEFORE STARTING THE SESSION
|
|
globalSessionId = sessionId;
|
|
registerSessionId(sessionId);
|
|
|
|
resetMessagesInTransmit(sessionId);
|
|
|
|
// RUN
|
|
int returnCode = ffprobe_execute(([arguments count] + 1), commandCharPArray);
|
|
|
|
// ALWAYS REMOVE THE ID FROM THE MAP
|
|
removeSession(sessionId);
|
|
|
|
// CLEANUP
|
|
av_free(commandCharPArray[0]);
|
|
av_free(commandCharPArray);
|
|
|
|
return returnCode;
|
|
}
|
|
|
|
@implementation FFmpegKitConfig
|
|
|
|
+ (void)initialize {
|
|
[ArchDetect class];
|
|
[FFmpegKit class];
|
|
[FFprobeKit class];
|
|
|
|
pipeIndexGenerator = [[AtomicLong alloc] initWithValue:1];
|
|
|
|
sessionHistorySize = 10;
|
|
sessionHistoryMap = [[NSMutableDictionary alloc] init];
|
|
sessionHistoryList = [[NSMutableArray alloc] init];
|
|
sessionHistoryLock = [[NSRecursiveLock alloc] init];
|
|
|
|
for(int i = 0; i<SESSION_MAP_SIZE; i++) {
|
|
atomic_init(&sessionMap[i], 0);
|
|
atomic_init(&sessionInTransitMessageCountMap[i], 0);
|
|
}
|
|
|
|
asyncDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
|
|
|
logCallback = nil;
|
|
statisticsCallback = nil;
|
|
ffmpegSessionCompleteCallback = nil;
|
|
ffprobeSessionCompleteCallback = nil;
|
|
mediaInformationSessionCompleteCallback = nil;
|
|
|
|
globalLogRedirectionStrategy = LogRedirectionStrategyPrintLogsWhenNoCallbacksDefined;
|
|
|
|
redirectionEnabled = 0;
|
|
lock = [[NSRecursiveLock alloc] init];
|
|
semaphore = dispatch_semaphore_create(0);
|
|
callbackDataArray = [[NSMutableArray alloc] init];
|
|
|
|
[FFmpegKitConfig enableRedirection];
|
|
}
|
|
|
|
+ (void)enableRedirection {
|
|
[lock lock];
|
|
|
|
if (redirectionEnabled != 0) {
|
|
[lock unlock];
|
|
return;
|
|
}
|
|
redirectionEnabled = 1;
|
|
|
|
[lock unlock];
|
|
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
callbackBlockFunction();
|
|
});
|
|
|
|
av_log_set_callback(ffmpegkit_log_callback_function);
|
|
set_report_callback(ffmpegkit_statistics_callback_function);
|
|
}
|
|
|
|
+ (void)disableRedirection {
|
|
[lock lock];
|
|
|
|
if (redirectionEnabled != 1) {
|
|
[lock unlock];
|
|
return;
|
|
}
|
|
redirectionEnabled = 0;
|
|
|
|
[lock unlock];
|
|
|
|
av_log_set_callback(av_log_default_callback);
|
|
set_report_callback(nil);
|
|
|
|
callbackNotify();
|
|
}
|
|
|
|
+ (int)setFontconfigConfigurationPath:(NSString*)path {
|
|
return [FFmpegKitConfig setEnvironmentVariable:@"FONTCONFIG_PATH" value:path];
|
|
}
|
|
|
|
+ (void)setFontDirectory:(NSString*)fontDirectoryPath with:(NSDictionary*)fontNameMapping {
|
|
[FFmpegKitConfig setFontDirectoryList:[NSArray arrayWithObject:fontDirectoryPath] with:fontNameMapping];
|
|
}
|
|
|
|
+ (void)setFontDirectoryList:(NSArray*)fontDirectoryArray with:(NSDictionary*)fontNameMapping {
|
|
NSError *error = nil;
|
|
BOOL isDirectory = YES;
|
|
BOOL isFile = NO;
|
|
int validFontNameMappingCount = 0;
|
|
NSString *tempConfigurationDirectory = [NSTemporaryDirectory() stringByAppendingPathComponent:@"fontconfig"];
|
|
NSString *fontConfigurationFile = [tempConfigurationDirectory stringByAppendingPathComponent:@"fonts.conf"];
|
|
|
|
if (![[NSFileManager defaultManager] fileExistsAtPath:tempConfigurationDirectory isDirectory:&isDirectory]) {
|
|
if (![[NSFileManager defaultManager] createDirectoryAtPath:tempConfigurationDirectory withIntermediateDirectories:YES attributes:nil error:&error]) {
|
|
NSLog(@"Failed to set font directory. Error received while creating temp conf directory: %@.", error);
|
|
return;
|
|
}
|
|
NSLog(@"Created temporary font conf directory: TRUE.");
|
|
}
|
|
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:fontConfigurationFile isDirectory:&isFile]) {
|
|
BOOL fontConfigurationDeleted = [[NSFileManager defaultManager] removeItemAtPath:fontConfigurationFile error:nil];
|
|
NSLog(@"Deleted old temporary font configuration: %s.", fontConfigurationDeleted?"TRUE":"FALSE");
|
|
}
|
|
|
|
/* PROCESS MAPPINGS FIRST */
|
|
NSString *fontNameMappingBlock = @"";
|
|
for (NSString *fontName in [fontNameMapping allKeys]) {
|
|
NSString *mappedFontName = [fontNameMapping objectForKey:fontName];
|
|
|
|
if ((fontName != nil) && (mappedFontName != nil) && ([fontName length] > 0) && ([mappedFontName length] > 0)) {
|
|
|
|
fontNameMappingBlock = [NSString stringWithFormat:@"%@\n%@\n%@%@%@\n%@\n%@\n%@%@%@\n%@\n%@\n",
|
|
@" <match target=\"pattern\">",
|
|
@" <test qual=\"any\" name=\"family\">",
|
|
@" <string>", fontName, @"</string>",
|
|
@" </test>",
|
|
@" <edit name=\"family\" mode=\"assign\" binding=\"same\">",
|
|
@" <string>", mappedFontName, @"</string>",
|
|
@" </edit>",
|
|
@" </match>"];
|
|
|
|
validFontNameMappingCount++;
|
|
}
|
|
}
|
|
|
|
NSMutableString *fontConfiguration = [NSMutableString stringWithFormat:@"%@\n%@\n%@\n%@\n",
|
|
@"<?xml version=\"1.0\"?>",
|
|
@"<!DOCTYPE fontconfig SYSTEM \"fonts.dtd\">",
|
|
@"<fontconfig>",
|
|
@" <dir prefix=\"cwd\">.</dir>"];
|
|
for (int i=0; i < [fontDirectoryArray count]; i++) {
|
|
NSString *fontDirectoryPath = [fontDirectoryArray objectAtIndex:i];
|
|
[fontConfiguration appendString: @" <dir>"];
|
|
[fontConfiguration appendString: fontDirectoryPath];
|
|
[fontConfiguration appendString: @"</dir>\n"];
|
|
}
|
|
[fontConfiguration appendString:fontNameMappingBlock];
|
|
[fontConfiguration appendString:@"</fontconfig>\n"];
|
|
|
|
if (![fontConfiguration writeToFile:fontConfigurationFile atomically:YES encoding:NSUTF8StringEncoding error:&error]) {
|
|
NSLog(@"Failed to set font directory. Error received while saving font configuration: %@.", error);
|
|
return;
|
|
}
|
|
|
|
NSLog(@"Saved new temporary font configuration with %d font name mappings.", validFontNameMappingCount);
|
|
|
|
[FFmpegKitConfig setFontconfigConfigurationPath:tempConfigurationDirectory];
|
|
|
|
for (int i=0; i < [fontDirectoryArray count]; i++) {
|
|
NSString *fontDirectoryPath = [fontDirectoryArray objectAtIndex:i];
|
|
NSLog(@"Font directory %@ registered successfully.", fontDirectoryPath);
|
|
}
|
|
}
|
|
|
|
+ (NSString*)registerNewFFmpegPipe {
|
|
NSError *error = nil;
|
|
BOOL isDirectory;
|
|
|
|
// PIPES ARE CREATED UNDER THE PIPES DIRECTORY
|
|
NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
|
|
NSString *pipesDir = [cacheDir stringByAppendingPathComponent:@"pipes"];
|
|
|
|
if (![[NSFileManager defaultManager] fileExistsAtPath:pipesDir isDirectory:&isDirectory]) {
|
|
if (![[NSFileManager defaultManager] createDirectoryAtPath:pipesDir withIntermediateDirectories:YES attributes:nil error:&error]) {
|
|
NSLog(@"Failed to create pipes directory: %@. Operation failed with %@.", pipesDir, error);
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
NSString *newFFmpegPipePath = [NSString stringWithFormat:@"%@/%@%ld", pipesDir, FFmpegKitNamedPipePrefix, [pipeIndexGenerator getAndIncrement]];
|
|
|
|
// FIRST CLOSE OLD PIPES WITH THE SAME NAME
|
|
[FFmpegKitConfig closeFFmpegPipe:newFFmpegPipePath];
|
|
|
|
int rc = mkfifo([newFFmpegPipePath UTF8String], S_IRWXU | S_IRWXG | S_IROTH);
|
|
if (rc == 0) {
|
|
return newFFmpegPipePath;
|
|
} else {
|
|
NSLog(@"Failed to register new FFmpeg pipe %@. Operation failed with rc=%d.", newFFmpegPipePath, rc);
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
+ (void)closeFFmpegPipe:(NSString*)ffmpegPipePath {
|
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
|
|
if ([fileManager fileExistsAtPath:ffmpegPipePath]){
|
|
[fileManager removeItemAtPath:ffmpegPipePath error:nil];
|
|
}
|
|
}
|
|
|
|
+ (NSString*)getFFmpegVersion {
|
|
return [NSString stringWithUTF8String:FFMPEG_VERSION];
|
|
}
|
|
|
|
+ (NSString*)getVersion {
|
|
if ([FFmpegKitConfig isLTSBuild] == 1) {
|
|
return [NSString stringWithFormat:@"%@-lts", FFmpegKitVersion];
|
|
} else {
|
|
return FFmpegKitVersion;
|
|
}
|
|
}
|
|
|
|
+ (int)isLTSBuild {
|
|
#if defined(FFMPEG_KIT_LTS)
|
|
return 1;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
+ (NSString*)getBuildDate {
|
|
char buildDate[10];
|
|
sprintf(buildDate, "%d", FFMPEG_KIT_BUILD_DATE);
|
|
return [NSString stringWithUTF8String:buildDate];
|
|
}
|
|
|
|
+ (int)setEnvironmentVariable:(NSString*)variableName value:(NSString*)variableValue {
|
|
return setenv([variableName UTF8String], [variableValue UTF8String], true);
|
|
}
|
|
|
|
+ (void)ignoreSignal:(Signal)signal {
|
|
if (signal == SignalQuit) {
|
|
handleSIGQUIT = 0;
|
|
} else if (signal == SignalInt) {
|
|
handleSIGINT = 0;
|
|
} else if (signal == SignalTerm) {
|
|
handleSIGTERM = 0;
|
|
} else if (signal == SignalXcpu) {
|
|
handleSIGXCPU = 0;
|
|
} else if (signal == SignalPipe) {
|
|
handleSIGPIPE = 0;
|
|
}
|
|
}
|
|
|
|
+ (void)ffmpegExecute:(FFmpegSession*)ffmpegSession {
|
|
[ffmpegSession startRunning];
|
|
|
|
@try {
|
|
int returnCode = executeFFmpeg([ffmpegSession getSessionId], [ffmpegSession getArguments]);
|
|
[ffmpegSession complete:[[ReturnCode alloc] init:returnCode]];
|
|
} @catch (NSException *exception) {
|
|
[ffmpegSession fail:exception];
|
|
NSLog(@"FFmpeg execute failed: %@.%@", [FFmpegKitConfig argumentsToString:[ffmpegSession getArguments]], [NSString stringWithFormat:@"%@", [exception callStackSymbols]]);
|
|
}
|
|
}
|
|
|
|
+ (void)ffprobeExecute:(FFprobeSession*)ffprobeSession {
|
|
[ffprobeSession startRunning];
|
|
|
|
@try {
|
|
int returnCode = executeFFprobe([ffprobeSession getSessionId], [ffprobeSession getArguments]);
|
|
[ffprobeSession complete:[[ReturnCode alloc] init:returnCode]];
|
|
} @catch (NSException *exception) {
|
|
[ffprobeSession fail:exception];
|
|
NSLog(@"FFprobe execute failed: %@.%@", [FFmpegKitConfig argumentsToString:[ffprobeSession getArguments]], [NSString stringWithFormat:@"%@", [exception callStackSymbols]]);
|
|
}
|
|
}
|
|
|
|
+ (void)getMediaInformationExecute:(MediaInformationSession*)mediaInformationSession withTimeout:(int)waitTimeout {
|
|
[mediaInformationSession startRunning];
|
|
|
|
@try {
|
|
int returnCodeValue = executeFFprobe([mediaInformationSession getSessionId], [mediaInformationSession getArguments]);
|
|
ReturnCode* returnCode = [[ReturnCode alloc] init:returnCodeValue];
|
|
[mediaInformationSession complete:returnCode];
|
|
if ([returnCode isValueSuccess]) {
|
|
NSArray* allLogs = [mediaInformationSession getAllLogsWithTimeout:waitTimeout];
|
|
NSMutableString* ffprobeJsonOutput = [[NSMutableString alloc] init];
|
|
for (int i=0; i < [allLogs count]; i++) {
|
|
Log* log = [allLogs objectAtIndex:i];
|
|
if ([log getLevel] == LevelAVLogStdErr) {
|
|
[ffprobeJsonOutput appendString:[log getMessage]];
|
|
}
|
|
}
|
|
MediaInformation* mediaInformation = [MediaInformationJsonParser fromWithError:ffprobeJsonOutput];
|
|
[mediaInformationSession setMediaInformation:mediaInformation];
|
|
}
|
|
} @catch (NSException *exception) {
|
|
[mediaInformationSession fail:exception];
|
|
NSLog(@"Get media information execute failed: %@.%@", [FFmpegKitConfig argumentsToString:[mediaInformationSession getArguments]], [NSString stringWithFormat:@"\n%@\n%@", [exception userInfo], [exception callStackSymbols]]);
|
|
}
|
|
}
|
|
|
|
+ (void)asyncFFmpegExecute:(FFmpegSession*)ffmpegSession {
|
|
[FFmpegKitConfig asyncFFmpegExecute:ffmpegSession onDispatchQueue:asyncDispatchQueue];
|
|
}
|
|
|
|
+ (void)asyncFFmpegExecute:(FFmpegSession*)ffmpegSession onDispatchQueue:(dispatch_queue_t)queue {
|
|
dispatch_async(queue, ^{
|
|
[FFmpegKitConfig ffmpegExecute:ffmpegSession];
|
|
|
|
FFmpegSessionCompleteCallback completeCallback = [ffmpegSession getCompleteCallback];
|
|
if (completeCallback != nil) {
|
|
@try {
|
|
// NOTIFY SESSION CALLBACK DEFINED
|
|
completeCallback(ffmpegSession);
|
|
}
|
|
@catch(NSException* exception) {
|
|
NSLog(@"Exception thrown inside session complete callback. %@", [exception callStackSymbols]);
|
|
}
|
|
}
|
|
|
|
FFmpegSessionCompleteCallback globalFFmpegSessionCompleteCallback = [FFmpegKitConfig getFFmpegSessionCompleteCallback];
|
|
if (globalFFmpegSessionCompleteCallback != nil) {
|
|
@try {
|
|
// NOTIFY SESSION CALLBACK DEFINED
|
|
globalFFmpegSessionCompleteCallback(ffmpegSession);
|
|
}
|
|
@catch(NSException* exception) {
|
|
NSLog(@"Exception thrown inside global complete callback. %@", [exception callStackSymbols]);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
+ (void)asyncFFprobeExecute:(FFprobeSession*)ffprobeSession {
|
|
[FFmpegKitConfig asyncFFprobeExecute:ffprobeSession onDispatchQueue:asyncDispatchQueue];
|
|
}
|
|
|
|
+ (void)asyncFFprobeExecute:(FFprobeSession*)ffprobeSession onDispatchQueue:(dispatch_queue_t)queue {
|
|
dispatch_async(queue, ^{
|
|
[FFmpegKitConfig ffprobeExecute:ffprobeSession];
|
|
|
|
FFprobeSessionCompleteCallback completeCallback = [ffprobeSession getCompleteCallback];
|
|
if (completeCallback != nil) {
|
|
@try {
|
|
// NOTIFY SESSION CALLBACK DEFINED
|
|
completeCallback(ffprobeSession);
|
|
}
|
|
@catch(NSException* exception) {
|
|
NSLog(@"Exception thrown inside session complete callback. %@", [exception callStackSymbols]);
|
|
}
|
|
}
|
|
|
|
FFprobeSessionCompleteCallback globalFFprobeSessionCompleteCallback = [FFmpegKitConfig getFFprobeSessionCompleteCallback];
|
|
if (globalFFprobeSessionCompleteCallback != nil) {
|
|
@try {
|
|
// NOTIFY SESSION CALLBACK DEFINED
|
|
globalFFprobeSessionCompleteCallback(ffprobeSession);
|
|
}
|
|
@catch(NSException* exception) {
|
|
NSLog(@"Exception thrown inside global complete callback. %@", [exception callStackSymbols]);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
+ (void)asyncGetMediaInformationExecute:(MediaInformationSession*)mediaInformationSession withTimeout:(int)waitTimeout {
|
|
[FFmpegKitConfig asyncGetMediaInformationExecute:mediaInformationSession onDispatchQueue:asyncDispatchQueue withTimeout:waitTimeout];
|
|
}
|
|
|
|
+ (void)asyncGetMediaInformationExecute:(MediaInformationSession*)mediaInformationSession onDispatchQueue:(dispatch_queue_t)queue withTimeout:(int)waitTimeout {
|
|
dispatch_async(queue, ^{
|
|
[FFmpegKitConfig getMediaInformationExecute:mediaInformationSession withTimeout:waitTimeout];
|
|
|
|
MediaInformationSessionCompleteCallback completeCallback = [mediaInformationSession getCompleteCallback];
|
|
if (completeCallback != nil) {
|
|
@try {
|
|
// NOTIFY SESSION CALLBACK DEFINED
|
|
completeCallback(mediaInformationSession);
|
|
}
|
|
@catch(NSException* exception) {
|
|
NSLog(@"Exception thrown inside session complete callback. %@", [exception callStackSymbols]);
|
|
}
|
|
}
|
|
|
|
MediaInformationSessionCompleteCallback globalMediaInformationSessionCompleteCallback = [FFmpegKitConfig getMediaInformationSessionCompleteCallback];
|
|
if (globalMediaInformationSessionCompleteCallback != nil) {
|
|
@try {
|
|
// NOTIFY SESSION CALLBACK DEFINED
|
|
globalMediaInformationSessionCompleteCallback(mediaInformationSession);
|
|
}
|
|
@catch(NSException* exception) {
|
|
NSLog(@"Exception thrown inside global complete callback. %@", [exception callStackSymbols]);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
+ (void)enableLogCallback:(LogCallback)callback {
|
|
logCallback = callback;
|
|
}
|
|
|
|
+ (void)enableStatisticsCallback:(StatisticsCallback)callback {
|
|
statisticsCallback = callback;
|
|
}
|
|
|
|
+ (void)enableFFmpegSessionCompleteCallback:(FFmpegSessionCompleteCallback)completeCallback {
|
|
ffmpegSessionCompleteCallback = completeCallback;
|
|
}
|
|
|
|
+ (FFmpegSessionCompleteCallback)getFFmpegSessionCompleteCallback {
|
|
return ffmpegSessionCompleteCallback;
|
|
}
|
|
|
|
+ (void)enableFFprobeSessionCompleteCallback:(FFprobeSessionCompleteCallback)completeCallback {
|
|
ffprobeSessionCompleteCallback = completeCallback;
|
|
}
|
|
|
|
+ (FFprobeSessionCompleteCallback)getFFprobeSessionCompleteCallback {
|
|
return ffprobeSessionCompleteCallback;
|
|
}
|
|
|
|
+ (void)enableMediaInformationSessionCompleteCallback:(MediaInformationSessionCompleteCallback)completeCallback {
|
|
mediaInformationSessionCompleteCallback = completeCallback;
|
|
}
|
|
|
|
+ (MediaInformationSessionCompleteCallback)getMediaInformationSessionCompleteCallback {
|
|
return mediaInformationSessionCompleteCallback;
|
|
}
|
|
|
|
+ (int)getLogLevel {
|
|
return configuredLogLevel;
|
|
}
|
|
|
|
+ (void)setLogLevel:(int)level {
|
|
configuredLogLevel = level;
|
|
}
|
|
|
|
+ (NSString*)logLevelToString:(int)level {
|
|
switch (level) {
|
|
case LevelAVLogStdErr: return @"STDERR";
|
|
case LevelAVLogTrace: return @"TRACE";
|
|
case LevelAVLogDebug: return @"DEBUG";
|
|
case LevelAVLogVerbose: return @"VERBOSE";
|
|
case LevelAVLogInfo: return @"INFO";
|
|
case LevelAVLogWarning: return @"WARNING";
|
|
case LevelAVLogError: return @"ERROR";
|
|
case LevelAVLogFatal: return @"FATAL";
|
|
case LevelAVLogPanic: return @"PANIC";
|
|
case LevelAVLogQuiet: return @"QUIET";
|
|
default: return @"";
|
|
}
|
|
}
|
|
|
|
+ (int)getSessionHistorySize {
|
|
return sessionHistorySize;
|
|
}
|
|
|
|
+ (void)setSessionHistorySize:(int)pSessionHistorySize {
|
|
if (pSessionHistorySize >= SESSION_MAP_SIZE) {
|
|
|
|
/*
|
|
* THERE IS A HARD LIMIT ON THE NATIVE SIDE. HISTORY SIZE MUST BE SMALLER THAN SESSION_MAP_SIZE
|
|
*/
|
|
@throw([NSException exceptionWithName:NSInvalidArgumentException reason:@"Session history size must not exceed the hard limit!" userInfo:nil]);
|
|
} else if (pSessionHistorySize > 0) {
|
|
sessionHistorySize = pSessionHistorySize;
|
|
deleteExpiredSessions();
|
|
}
|
|
}
|
|
|
|
+ (id<Session>)getSession:(long)sessionId {
|
|
[sessionHistoryLock lock];
|
|
|
|
id<Session> session = [sessionHistoryMap objectForKey:[NSNumber numberWithLong:sessionId]];
|
|
|
|
[sessionHistoryLock unlock];
|
|
|
|
return session;
|
|
}
|
|
|
|
+ (id<Session>)getLastSession {
|
|
[sessionHistoryLock lock];
|
|
|
|
id<Session> lastSession = [sessionHistoryList lastObject];
|
|
|
|
[sessionHistoryLock unlock];
|
|
|
|
return lastSession;
|
|
}
|
|
|
|
+ (id<Session>)getLastCompletedSession {
|
|
id<Session> lastCompletedSession = nil;
|
|
|
|
[sessionHistoryLock lock];
|
|
|
|
for(int i = [sessionHistoryList count] - 1; i >= 0; i--) {
|
|
id<Session> session = [sessionHistoryList objectAtIndex:i];
|
|
if ([session getState] == SessionStateCompleted) {
|
|
lastCompletedSession = session;
|
|
break;
|
|
}
|
|
}
|
|
|
|
[sessionHistoryLock unlock];
|
|
|
|
return lastCompletedSession;
|
|
}
|
|
|
|
+ (NSArray*)getSessions {
|
|
[sessionHistoryLock lock];
|
|
|
|
NSArray* sessionsCopy = [sessionHistoryList copy];
|
|
|
|
[sessionHistoryLock unlock];
|
|
|
|
return sessionsCopy;
|
|
}
|
|
|
|
+ (void)clearSessions {
|
|
[sessionHistoryLock lock];
|
|
|
|
[sessionHistoryList removeAllObjects];
|
|
[sessionHistoryMap removeAllObjects];
|
|
|
|
[sessionHistoryLock unlock];
|
|
}
|
|
|
|
+ (NSArray*)getFFmpegSessions {
|
|
NSMutableArray* ffmpegSessions = [[NSMutableArray alloc] init];
|
|
|
|
[sessionHistoryLock lock];
|
|
|
|
for(int i = 0; i < [sessionHistoryList count]; i++) {
|
|
id<Session> session = [sessionHistoryList objectAtIndex:i];
|
|
if ([session isFFmpeg]) {
|
|
[ffmpegSessions addObject:session];
|
|
}
|
|
}
|
|
|
|
[sessionHistoryLock unlock];
|
|
|
|
return ffmpegSessions;
|
|
}
|
|
|
|
+ (NSArray*)getFFprobeSessions {
|
|
NSMutableArray* ffprobeSessions = [[NSMutableArray alloc] init];
|
|
|
|
[sessionHistoryLock lock];
|
|
|
|
for(int i = 0; i < [sessionHistoryList count]; i++) {
|
|
id<Session> session = [sessionHistoryList objectAtIndex:i];
|
|
if ([session isFFprobe]) {
|
|
[ffprobeSessions addObject:session];
|
|
}
|
|
}
|
|
|
|
[sessionHistoryLock unlock];
|
|
|
|
return ffprobeSessions;
|
|
}
|
|
|
|
+ (NSArray*)getMediaInformationSessions {
|
|
NSMutableArray* mediaInformationSessions = [[NSMutableArray alloc] init];
|
|
|
|
[sessionHistoryLock lock];
|
|
|
|
for(int i = 0; i < [sessionHistoryList count]; i++) {
|
|
id<Session> session = [sessionHistoryList objectAtIndex:i];
|
|
if ([session isMediaInformation]) {
|
|
[mediaInformationSessions addObject:session];
|
|
}
|
|
}
|
|
|
|
[sessionHistoryLock unlock];
|
|
|
|
return mediaInformationSessions;
|
|
}
|
|
|
|
+ (NSArray*)getSessionsByState:(SessionState)state {
|
|
NSMutableArray* sessions = [[NSMutableArray alloc] init];
|
|
|
|
[sessionHistoryLock lock];
|
|
|
|
for(int i = 0; i < [sessionHistoryList count]; i++) {
|
|
id<Session> session = [sessionHistoryList objectAtIndex:i];
|
|
if ([session getState] == state) {
|
|
[sessions addObject:session];
|
|
}
|
|
}
|
|
|
|
[sessionHistoryLock unlock];
|
|
|
|
return sessions;
|
|
}
|
|
|
|
+ (LogRedirectionStrategy)getLogRedirectionStrategy {
|
|
return globalLogRedirectionStrategy;
|
|
}
|
|
|
|
+ (void)setLogRedirectionStrategy:(LogRedirectionStrategy)logRedirectionStrategy {
|
|
globalLogRedirectionStrategy = logRedirectionStrategy;
|
|
}
|
|
|
|
+ (int)messagesInTransmit:(long)sessionId {
|
|
return atomic_load(&sessionInTransitMessageCountMap[sessionId % SESSION_MAP_SIZE]);
|
|
}
|
|
|
|
+ (NSString*)sessionStateToString:(SessionState)state {
|
|
switch (state) {
|
|
case SessionStateCreated: return @"CREATED";
|
|
case SessionStateRunning: return @"RUNNING";
|
|
case SessionStateFailed: return @"FAILED";
|
|
case SessionStateCompleted: return @"COMPLETED";
|
|
default: return @"";
|
|
}
|
|
}
|
|
|
|
+ (NSArray*)parseArguments:(NSString*)command {
|
|
NSMutableArray *argumentArray = [[NSMutableArray alloc] init];
|
|
NSMutableString *currentArgument = [[NSMutableString alloc] init];
|
|
|
|
bool singleQuoteStarted = false;
|
|
bool doubleQuoteStarted = false;
|
|
|
|
for (int i = 0; i < command.length; i++) {
|
|
unichar previousChar;
|
|
if (i > 0) {
|
|
previousChar = [command characterAtIndex:(i - 1)];
|
|
} else {
|
|
previousChar = 0;
|
|
}
|
|
unichar currentChar = [command characterAtIndex:i];
|
|
|
|
if (currentChar == ' ') {
|
|
if (singleQuoteStarted || doubleQuoteStarted) {
|
|
[currentArgument appendFormat: @"%C", currentChar];
|
|
} else if ([currentArgument length] > 0) {
|
|
[argumentArray addObject: currentArgument];
|
|
currentArgument = [[NSMutableString alloc] init];
|
|
}
|
|
} else if (currentChar == '\'' && (previousChar == 0 || previousChar != '\\')) {
|
|
if (singleQuoteStarted) {
|
|
singleQuoteStarted = false;
|
|
} else if (doubleQuoteStarted) {
|
|
[currentArgument appendFormat: @"%C", currentChar];
|
|
} else {
|
|
singleQuoteStarted = true;
|
|
}
|
|
} else if (currentChar == '\"' && (previousChar == 0 || previousChar != '\\')) {
|
|
if (doubleQuoteStarted) {
|
|
doubleQuoteStarted = false;
|
|
} else if (singleQuoteStarted) {
|
|
[currentArgument appendFormat: @"%C", currentChar];
|
|
} else {
|
|
doubleQuoteStarted = true;
|
|
}
|
|
} else {
|
|
[currentArgument appendFormat: @"%C", currentChar];
|
|
}
|
|
}
|
|
|
|
if ([currentArgument length] > 0) {
|
|
[argumentArray addObject: currentArgument];
|
|
}
|
|
|
|
return argumentArray;
|
|
}
|
|
|
|
+ (NSString*)argumentsToString:(NSArray*)arguments {
|
|
if (arguments == nil) {
|
|
return @"nil";
|
|
}
|
|
|
|
NSMutableString *string = [NSMutableString stringWithString:@""];
|
|
for (int i=0; i < [arguments count]; i++) {
|
|
NSString *argument = [arguments objectAtIndex:i];
|
|
if (i > 0) {
|
|
[string appendString:@" "];
|
|
}
|
|
[string appendString:argument];
|
|
}
|
|
|
|
return string;
|
|
}
|
|
|
|
@end
|
|
|