FFmpegKit iOS / macOS / tvOS API  4.4
FFmpegKitConfig.m
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2018-2021 Taner Sener
3  *
4  * This file is part of FFmpegKit.
5  *
6  * FFmpegKit is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * FFmpegKit is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #import <stdatomic.h>
21 #import <sys/types.h>
22 #import <sys/stat.h>
23 #import "libavutil/ffversion.h"
24 #import "fftools_ffmpeg.h"
25 #import "ArchDetect.h"
26 #import "AtomicLong.h"
27 #import "FFmpegKit.h"
28 #import "FFmpegKitConfig.h"
29 #import "FFmpegSession.h"
30 #import "FFprobeKit.h"
31 #import "FFprobeSession.h"
32 #import "Level.h"
35 #import "SessionState.h"
36 
38 NSString* const FFmpegKitVersion = @"4.4";
39 
43 NSString* const FFmpegKitNamedPipePrefix = @"fk_pipe_";
44 
49 
50 /* Session history variables */
51 static int sessionHistorySize;
52 static volatile NSMutableDictionary* sessionHistoryMap;
53 static NSMutableArray* sessionHistoryList;
54 static NSRecursiveLock* sessionHistoryLock;
55 
57 const int SESSION_MAP_SIZE = 1000;
58 static atomic_short sessionMap[SESSION_MAP_SIZE];
59 static atomic_int sessionInTransitMessageCountMap[SESSION_MAP_SIZE];
60 
61 static dispatch_queue_t asyncDispatchQueue;
62 
65 
68 
71 
72 static LogRedirectionStrategy globalLogRedirectionStrategy;
73 
75 static int redirectionEnabled;
76 static NSRecursiveLock *lock;
77 static dispatch_semaphore_t semaphore;
78 static NSMutableArray *callbackDataArray;
79 
81 volatile int handleSIGQUIT = 1;
82 volatile int handleSIGINT = 1;
83 volatile int handleSIGTERM = 1;
84 volatile int handleSIGXCPU = 1;
85 volatile int handleSIGPIPE = 1;
86 
88 __thread volatile long _sessionId = 0;
89 
91 int configuredLogLevel = LevelAVLogInfo;
92 
94 int ffmpeg_execute(int argc, char **argv);
95 
97 int ffprobe_execute(int argc, char **argv);
98 
99 typedef NS_ENUM(NSUInteger, CallbackType) {
100  LogType,
101  StatisticsType
102 };
103 
107 @interface CallbackData : NSObject
108 
109 @end
110 
111 @implementation CallbackData {
112  CallbackType _type;
113  long _sessionId; // session id
114 
115  int _logLevel; // log level
116  NSString* _logData; // log data
117 
118  int _statisticsFrameNumber; // statistics frame number
119  float _statisticsFps; // statistics fps
120  float _statisticsQuality; // statistics quality
121  int64_t _statisticsSize; // statistics size
122  int _statisticsTime; // statistics time
123  double _statisticsBitrate; // statistics bitrate
124  double _statisticsSpeed; // statistics speed
125 }
126 
127  - (instancetype)init:(long)sessionId logLevel:(int)logLevel data:(NSString*)logData {
128  self = [super init];
129  if (self) {
130  _type = LogType;
131  _sessionId = sessionId;
132  _logLevel = logLevel;
133  _logData = logData;
134  }
135 
136  return self;
137 }
138 
139  - (instancetype)init:(long)sessionId
140  videoFrameNumber:(int)videoFrameNumber
141  fps:(float)videoFps
142  quality:(float)videoQuality
143  size:(int64_t)size
144  time:(int)time
145  bitrate:(double)bitrate
146  speed:(double)speed {
147  self = [super init];
148  if (self) {
149  _type = StatisticsType;
150  _sessionId = sessionId;
151  _statisticsFrameNumber = videoFrameNumber;
152  _statisticsFps = videoFps;
153  _statisticsQuality = videoQuality;
154  _statisticsSize = size;
155  _statisticsTime = time;
156  _statisticsBitrate = bitrate;
157  _statisticsSpeed = speed;
158  }
159 
160  return self;
161 }
162 
163 - (CallbackType)getType {
164  return _type;
165 }
166 
167 - (long)getSessionId {
168  return _sessionId;
169 }
170 
171 - (int)getLogLevel {
172  return _logLevel;
173 }
174 
175 - (NSString*)getLogData {
176  return _logData;
177 }
178 
179 - (int)getStatisticsFrameNumber {
180  return _statisticsFrameNumber;
181 }
182 
183 - (float)getStatisticsFps {
184  return _statisticsFps;
185 }
186 
187 - (float)getStatisticsQuality {
188  return _statisticsQuality;
189 }
190 
191 - (int64_t)getStatisticsSize {
192  return _statisticsSize;
193 }
194 
195 - (int)getStatisticsTime {
196  return _statisticsTime;
197 }
198 
199 - (double)getStatisticsBitrate {
200  return _statisticsBitrate;
201 }
202 
203 - (double)getStatisticsSpeed {
204  return _statisticsSpeed;
205 }
206 
207 @end
208 
214 void callbackWait(int milliSeconds) {
215  dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(milliSeconds * NSEC_PER_MSEC)));
216 }
217 
222  dispatch_semaphore_signal(semaphore);
223 }
224 
231 void logCallbackDataAdd(int level, NSString *logData) {
232  CallbackData* callbackData = [[CallbackData alloc] init:_sessionId logLevel:level data:logData];
233 
234  [lock lock];
235  [callbackDataArray addObject:callbackData];
236  [lock unlock];
237 
238  callbackNotify();
239 
241 }
242 
246 void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed) {
247  CallbackData *callbackData = [[CallbackData alloc] init:_sessionId videoFrameNumber:frameNumber fps:fps quality:quality size:size time:time bitrate:bitrate speed:speed];
248 
249  [lock lock];
250  [callbackDataArray addObject:callbackData];
251  [lock unlock];
252 
253  callbackNotify();
254 
256 }
257 
262  CallbackData *newData = nil;
263 
264  [lock lock];
265 
266  @try {
267  if ([callbackDataArray count] > 0) {
268  newData = [callbackDataArray objectAtIndex:0];
269  [callbackDataArray removeObjectAtIndex:0];
270  }
271  } @catch(NSException *exception) {
272  // DO NOTHING
273  } @finally {
274  [lock unlock];
275  }
276 
277  return newData;
278 }
279 
285 void addSession(long sessionId) {
286  atomic_store(&sessionMap[sessionId % SESSION_MAP_SIZE], 1);
287 }
288 
294 void removeSession(long sessionId) {
295  atomic_store(&sessionMap[sessionId % SESSION_MAP_SIZE], 0);
296 }
297 
303 void cancelSession(long sessionId) {
304  atomic_store(&sessionMap[sessionId % SESSION_MAP_SIZE], 2);
305 }
306 
313 int cancelRequested(long sessionId) {
314  if (atomic_load(&sessionMap[sessionId % SESSION_MAP_SIZE]) == 2) {
315  return 1;
316  } else {
317  return 0;
318  }
319 }
320 
326 void resetMessagesInTransmit(long sessionId) {
327  atomic_store(&sessionInTransitMessageCountMap[sessionId % SESSION_MAP_SIZE], 0);
328 }
329 
338 void ffmpegkit_log_callback_function(void *ptr, int level, const char* format, va_list vargs) {
339 
340  // DO NOT PROCESS UNWANTED LOGS
341  if (level >= 0) {
342  level &= 0xff;
343  }
344  int activeLogLevel = av_log_get_level();
345 
346  // LevelAVLogStdErr logs are always redirected
347  if ((activeLogLevel == LevelAVLogQuiet && level != LevelAVLogStdErr) || (level > activeLogLevel)) {
348  return;
349  }
350 
351  NSString *logData = [[NSString alloc] initWithFormat:[NSString stringWithCString:format encoding:NSUTF8StringEncoding] arguments:vargs];
352 
353  if (logData.length > 0) {
354  logCallbackDataAdd(level, logData);
355  }
356 }
357 
369 void ffmpegkit_statistics_callback_function(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed) {
370  statisticsCallbackDataAdd(frameNumber, fps, quality, size, time, bitrate, speed);
371 }
372 
373 void process_log(long sessionId, int levelValue, NSString* logMessage) {
374  int activeLogLevel = av_log_get_level();
375  Log* log = [[Log alloc] init:sessionId:levelValue:logMessage];
376  BOOL globalCallbackDefined = false;
377  BOOL sessionCallbackDefined = false;
378  LogRedirectionStrategy activeLogRedirectionStrategy = globalLogRedirectionStrategy;
379 
380  // LevelAVLogStdErr logs are always redirected
381  if ((activeLogLevel == LevelAVLogQuiet && levelValue != LevelAVLogStdErr) || (levelValue > activeLogLevel)) {
382  // LOG NEITHER PRINTED NOR FORWARDED
383  return;
384  }
385 
386  id<Session> session = [FFmpegKitConfig getSession:sessionId];
387  if (session != nil) {
388  activeLogRedirectionStrategy = [session getLogRedirectionStrategy];
389  [session addLog:log];
390 
391  LogCallback sessionLogCallback = [session getLogCallback];
392  if (sessionLogCallback != nil) {
393  sessionCallbackDefined = TRUE;
394 
395  @try {
396  // NOTIFY SESSION CALLBACK DEFINED
397  sessionLogCallback(log);
398  }
399  @catch(NSException* exception) {
400  NSLog(@"Exception thrown inside session LogCallback block. %@", [exception callStackSymbols]);
401  }
402  }
403  }
404 
405  LogCallback globalLogCallback = logCallback;
406  if (globalLogCallback != nil) {
407  globalCallbackDefined = TRUE;
408 
409  @try {
410  // NOTIFY GLOBAL CALLBACK DEFINED
411  globalLogCallback(log);
412  }
413  @catch(NSException* exception) {
414  NSLog(@"Exception thrown inside global LogCallback block. %@", [exception callStackSymbols]);
415  }
416  }
417 
418  // EXECUTE THE LOG STRATEGY
419  switch (activeLogRedirectionStrategy) {
420  case LogRedirectionStrategyNeverPrintLogs: {
421  return;
422  }
423  case LogRedirectionStrategyPrintLogsWhenGlobalCallbackNotDefined: {
424  if (globalCallbackDefined) {
425  return;
426  }
427  }
428  break;
429  case LogRedirectionStrategyPrintLogsWhenSessionCallbackNotDefined: {
430  if (sessionCallbackDefined) {
431  return;
432  }
433  }
434  case LogRedirectionStrategyPrintLogsWhenNoCallbacksDefined: {
435  if (globalCallbackDefined || sessionCallbackDefined) {
436  return;
437  }
438  }
439  case LogRedirectionStrategyAlwaysPrintLogs: {
440  }
441  }
442 
443  // PRINT LOGS
444  switch (levelValue) {
445  case LevelAVLogQuiet:
446  // PRINT NO OUTPUT
447  break;
448  default:
449  // WRITE TO NSLOG
450  NSLog(@"%@: %@", [FFmpegKitConfig logLevelToString:levelValue], logMessage);
451  break;
452  }
453 }
454 
455 void process_statistics(long sessionId, int videoFrameNumber, float videoFps, float videoQuality, long size, int time, double bitrate, double speed) {
456 
457  Statistics *statistics = [[Statistics alloc] init:sessionId videoFrameNumber:videoFrameNumber videoFps:videoFps videoQuality:videoQuality size:size time:time bitrate:bitrate speed:speed];
458 
459  id<Session> session = [FFmpegKitConfig getSession:sessionId];
460  if (session != nil && [session isFFmpeg]) {
461  FFmpegSession *ffmpegSession = (FFmpegSession*)session;
462  [ffmpegSession addStatistics:statistics];
463 
464  StatisticsCallback sessionStatisticsCallback = [ffmpegSession getStatisticsCallback];
465  if (sessionStatisticsCallback != nil) {
466  @try {
467  sessionStatisticsCallback(statistics);
468  }
469  @catch(NSException* exception) {
470  NSLog(@"Exception thrown inside session StatisticsCallback block. %@", [exception callStackSymbols]);
471  }
472  }
473  }
474 
475  StatisticsCallback globalStatisticsCallback = statisticsCallback;
476  if (globalStatisticsCallback != nil) {
477  @try {
478  globalStatisticsCallback(statistics);
479  }
480  @catch(NSException* exception) {
481  NSLog(@"Exception thrown inside global StatisticsCallback block. %@", [exception callStackSymbols]);
482  }
483  }
484 }
485 
490  int activeLogLevel = av_log_get_level();
491  if ((activeLogLevel != LevelAVLogQuiet) && (LevelAVLogDebug <= activeLogLevel)) {
492  NSLog(@"Async callback block started.\n");
493  }
494 
495  while(redirectionEnabled) {
496  @autoreleasepool {
497  @try {
498 
499  CallbackData *callbackData = callbackDataRemove();
500  if (callbackData != nil) {
501 
502  if ([callbackData getType] == LogType) {
503  process_log([callbackData getSessionId], [callbackData getLogLevel], [callbackData getLogData]);
504  } else {
505  process_statistics([callbackData getSessionId],
506  [callbackData getStatisticsFrameNumber],
507  [callbackData getStatisticsFps],
508  [callbackData getStatisticsQuality],
509  [callbackData getStatisticsSize],
510  [callbackData getStatisticsTime],
511  [callbackData getStatisticsBitrate],
512  [callbackData getStatisticsSpeed]);
513  }
514 
515  atomic_fetch_sub(&sessionInTransitMessageCountMap[[callbackData getSessionId] % SESSION_MAP_SIZE], 1);
516 
517  } else {
518  callbackWait(100);
519  }
520 
521  } @catch(NSException *exception) {
522  activeLogLevel = av_log_get_level();
523  if ((activeLogLevel != LevelAVLogQuiet) && (LevelAVLogWarning <= activeLogLevel)) {
524  NSLog(@"Async callback block received error: %@n\n", exception);
525  NSLog(@"%@", [exception callStackSymbols]);
526  }
527  }
528  }
529  }
530 
531  activeLogLevel = av_log_get_level();
532  if ((activeLogLevel != LevelAVLogQuiet) && (LevelAVLogDebug <= activeLogLevel)) {
533  NSLog(@"Async callback block stopped.\n");
534  }
535 }
536 
537 int executeFFmpeg(long sessionId, NSArray* arguments) {
538  NSString* const LIB_NAME = @"ffmpeg";
539 
540  // SETS DEFAULT LOG LEVEL BEFORE STARTING A NEW RUN
541  av_log_set_level(configuredLogLevel);
542 
543  char **commandCharPArray = (char **)av_malloc(sizeof(char*) * ([arguments count] + 1));
544 
545  /* PRESERVE USAGE FORMAT
546  *
547  * ffmpeg <arguments>
548  */
549  commandCharPArray[0] = (char *)av_malloc(sizeof(char) * ([LIB_NAME length] + 1));
550  strcpy(commandCharPArray[0], [LIB_NAME UTF8String]);
551 
552  // PREPARE ARRAY ELEMENTS
553  for (int i=0; i < [arguments count]; i++) {
554  NSString *argument = [arguments objectAtIndex:i];
555  commandCharPArray[i + 1] = (char *) [argument UTF8String];
556  }
557 
558  // REGISTER THE ID BEFORE STARTING THE SESSION
559  _sessionId = sessionId;
560  addSession(sessionId);
561 
562  resetMessagesInTransmit(sessionId);
563 
564  // RUN
565  int returnCode = ffmpeg_execute(([arguments count] + 1), commandCharPArray);
566 
567  // ALWAYS REMOVE THE ID FROM THE MAP
568  removeSession(sessionId);
569 
570  // CLEANUP
571  av_free(commandCharPArray[0]);
572  av_free(commandCharPArray);
573 
574  return returnCode;
575 }
576 
577 int executeFFprobe(long sessionId, NSArray* arguments) {
578  NSString* const LIB_NAME = @"ffprobe";
579 
580  // SETS DEFAULT LOG LEVEL BEFORE STARTING A NEW RUN
581  av_log_set_level(configuredLogLevel);
582 
583  char **commandCharPArray = (char **)av_malloc(sizeof(char*) * ([arguments count] + 1));
584 
585  /* PRESERVE USAGE FORMAT
586  *
587  * ffprobe <arguments>
588  */
589  commandCharPArray[0] = (char *)av_malloc(sizeof(char) * ([LIB_NAME length] + 1));
590  strcpy(commandCharPArray[0], [LIB_NAME UTF8String]);
591 
592  // PREPARE ARRAY ELEMENTS
593  for (int i=0; i < [arguments count]; i++) {
594  NSString *argument = [arguments objectAtIndex:i];
595  commandCharPArray[i + 1] = (char *) [argument UTF8String];
596  }
597 
598  // REGISTER THE ID BEFORE STARTING THE SESSION
599  _sessionId = sessionId;
600  addSession(sessionId);
601 
602  resetMessagesInTransmit(sessionId);
603 
604  // RUN
605  int returnCode = ffprobe_execute(([arguments count] + 1), commandCharPArray);
606 
607  // ALWAYS REMOVE THE ID FROM THE MAP
608  removeSession(sessionId);
609 
610  // CLEANUP
611  av_free(commandCharPArray[0]);
612  av_free(commandCharPArray);
613 
614  return returnCode;
615 }
616 
617 @implementation FFmpegKitConfig
618 
619 + (void)initialize {
620  [ArchDetect class];
621  [FFmpegKit class];
622  [FFprobeKit class];
623 
624  pipeIndexGenerator = [[AtomicLong alloc] initWithValue:1];
625 
626  sessionHistorySize = 10;
627  sessionHistoryMap = [[NSMutableDictionary alloc] init];
628  sessionHistoryList = [[NSMutableArray alloc] init];
629  sessionHistoryLock = [[NSRecursiveLock alloc] init];
630 
631  for(int i = 0; i<SESSION_MAP_SIZE; i++) {
632  atomic_init(&sessionMap[i], 0);
633  atomic_init(&sessionInTransitMessageCountMap[i], 0);
634  }
635 
636  asyncDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
637 
638  logCallback = nil;
639  statisticsCallback = nil;
640  executeCallback = nil;
641 
642  globalLogRedirectionStrategy = LogRedirectionStrategyPrintLogsWhenNoCallbacksDefined;
643 
644  redirectionEnabled = 0;
645  lock = [[NSRecursiveLock alloc] init];
646  semaphore = dispatch_semaphore_create(0);
647  callbackDataArray = [[NSMutableArray alloc] init];
648 
650 }
651 
652 + (void)enableRedirection {
653  [lock lock];
654 
655  if (redirectionEnabled != 0) {
656  [lock unlock];
657  return;
658  }
659  redirectionEnabled = 1;
660 
661  [lock unlock];
662 
663  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
665  });
666 
667  av_log_set_callback(ffmpegkit_log_callback_function);
669 }
670 
671 + (void)disableRedirection {
672  [lock lock];
673 
674  if (redirectionEnabled != 1) {
675  [lock unlock];
676  return;
677  }
678  redirectionEnabled = 0;
679 
680  [lock unlock];
681 
682  av_log_set_callback(av_log_default_callback);
683  set_report_callback(nil);
684 
685  callbackNotify();
686 }
687 
688 + (int)setFontconfigConfigurationPath:(NSString*)path {
689  return [FFmpegKitConfig setEnvironmentVariable:@"FONTCONFIG_PATH" value:path];
690 }
691 
692 + (void)setFontDirectory:(NSString*)fontDirectoryPath with:(NSDictionary*)fontNameMapping {
693  [FFmpegKitConfig setFontDirectoryList:[NSArray arrayWithObject:fontDirectoryPath] with:fontNameMapping];
694 }
695 
696 + (void)setFontDirectoryList:(NSArray*)fontDirectoryArray with:(NSDictionary*)fontNameMapping {
697  NSError *error = nil;
698  BOOL isDirectory = YES;
699  BOOL isFile = NO;
700  int validFontNameMappingCount = 0;
701  NSString *tempConfigurationDirectory = [NSTemporaryDirectory() stringByAppendingPathComponent:@"fontconfig"];
702  NSString *fontConfigurationFile = [tempConfigurationDirectory stringByAppendingPathComponent:@"fonts.conf"];
703 
704  if (![[NSFileManager defaultManager] fileExistsAtPath:tempConfigurationDirectory isDirectory:&isDirectory]) {
705  if (![[NSFileManager defaultManager] createDirectoryAtPath:tempConfigurationDirectory withIntermediateDirectories:YES attributes:nil error:&error]) {
706  NSLog(@"Failed to set font directory. Error received while creating temp conf directory: %@.", error);
707  return;
708  }
709  NSLog(@"Created temporary font conf directory: TRUE.");
710  }
711 
712  if ([[NSFileManager defaultManager] fileExistsAtPath:fontConfigurationFile isDirectory:&isFile]) {
713  BOOL fontConfigurationDeleted = [[NSFileManager defaultManager] removeItemAtPath:fontConfigurationFile error:nil];
714  NSLog(@"Deleted old temporary font configuration: %s.", fontConfigurationDeleted?"TRUE":"FALSE");
715  }
716 
717  /* PROCESS MAPPINGS FIRST */
718  NSString *fontNameMappingBlock = @"";
719  for (NSString *fontName in [fontNameMapping allKeys]) {
720  NSString *mappedFontName = [fontNameMapping objectForKey:fontName];
721 
722  if ((fontName != nil) && (mappedFontName != nil) && ([fontName length] > 0) && ([mappedFontName length] > 0)) {
723 
724  fontNameMappingBlock = [NSString stringWithFormat:@"%@\n%@\n%@%@%@\n%@\n%@\n%@%@%@\n%@\n%@\n",
725  @" <match target=\"pattern\">",
726  @" <test qual=\"any\" name=\"family\">",
727  @" <string>", fontName, @"</string>",
728  @" </test>",
729  @" <edit name=\"family\" mode=\"assign\" binding=\"same\">",
730  @" <string>", mappedFontName, @"</string>",
731  @" </edit>",
732  @" </match>"];
733 
734  validFontNameMappingCount++;
735  }
736  }
737 
738  NSMutableString *fontConfiguration = [NSMutableString stringWithFormat:@"%@\n%@\n%@\n%@\n",
739  @"<?xml version=\"1.0\"?>",
740  @"<!DOCTYPE fontconfig SYSTEM \"fonts.dtd\">",
741  @"<fontconfig>",
742  @" <dir prefix=\"cwd\">.</dir>"];
743  for (int i=0; i < [fontDirectoryArray count]; i++) {
744  NSString *fontDirectoryPath = [fontDirectoryArray objectAtIndex:i];
745  [fontConfiguration appendString: @" <dir>"];
746  [fontConfiguration appendString: fontDirectoryPath];
747  [fontConfiguration appendString: @"</dir>"];
748  }
749  [fontConfiguration appendString:fontNameMappingBlock];
750  [fontConfiguration appendString:@"</fontconfig>"];
751 
752  if (![fontConfiguration writeToFile:fontConfigurationFile atomically:YES encoding:NSUTF8StringEncoding error:&error]) {
753  NSLog(@"Failed to set font directory. Error received while saving font configuration: %@.", error);
754  return;
755  }
756 
757  NSLog(@"Saved new temporary font configuration with %d font name mappings.", validFontNameMappingCount);
758 
759  [FFmpegKitConfig setFontconfigConfigurationPath:tempConfigurationDirectory];
760 
761  for (int i=0; i < [fontDirectoryArray count]; i++) {
762  NSString *fontDirectoryPath = [fontDirectoryArray objectAtIndex:i];
763  NSLog(@"Font directory %@ registered successfully.", fontDirectoryPath);
764  }
765 }
766 
767 + (NSString*)registerNewFFmpegPipe {
768  NSError *error = nil;
769  BOOL isDirectory;
770 
771  // PIPES ARE CREATED UNDER THE PIPES DIRECTORY
772  NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
773  NSString *pipesDir = [cacheDir stringByAppendingPathComponent:@"pipes"];
774 
775  if (![[NSFileManager defaultManager] fileExistsAtPath:pipesDir isDirectory:&isDirectory]) {
776  if (![[NSFileManager defaultManager] createDirectoryAtPath:pipesDir withIntermediateDirectories:YES attributes:nil error:&error]) {
777  NSLog(@"Failed to create pipes directory: %@. Operation failed with %@.", pipesDir, error);
778  return nil;
779  }
780  }
781 
782  NSString *newFFmpegPipePath = [NSString stringWithFormat:@"%@/%@%ld", pipesDir, FFmpegKitNamedPipePrefix, [pipeIndexGenerator getAndIncrement]];
783 
784  // FIRST CLOSE OLD PIPES WITH THE SAME NAME
785  [FFmpegKitConfig closeFFmpegPipe:newFFmpegPipePath];
786 
787  int rc = mkfifo([newFFmpegPipePath UTF8String], S_IRWXU | S_IRWXG | S_IROTH);
788  if (rc == 0) {
789  return newFFmpegPipePath;
790  } else {
791  NSLog(@"Failed to register new FFmpeg pipe %@. Operation failed with rc=%d.", newFFmpegPipePath, rc);
792  return nil;
793  }
794 }
795 
796 + (void)closeFFmpegPipe:(NSString*)ffmpegPipePath {
797  NSFileManager *fileManager = [NSFileManager defaultManager];
798 
799  if ([fileManager fileExistsAtPath:ffmpegPipePath]){
800  [fileManager removeItemAtPath:ffmpegPipePath error:nil];
801  }
802 }
803 
804 + (NSString*)getFFmpegVersion {
805  return [NSString stringWithUTF8String:FFMPEG_VERSION];
806 }
807 
808 + (NSString*)getVersion {
809  if ([FFmpegKitConfig isLTSBuild] == 1) {
810  return [NSString stringWithFormat:@"%@-lts", FFmpegKitVersion];
811  } else {
812  return FFmpegKitVersion;
813  }
814 }
815 
816 + (int)isLTSBuild {
817  #if defined(FFMPEG_KIT_LTS)
818  return 1;
819  #else
820  return 0;
821  #endif
822 }
823 
824 + (NSString*)getBuildDate {
825  char buildDate[10];
826  sprintf(buildDate, "%d", FFMPEG_KIT_BUILD_DATE);
827  return [NSString stringWithUTF8String:buildDate];
828 }
829 
830 + (int)setEnvironmentVariable:(NSString*)variableName value:(NSString*)variableValue {
831  return setenv([variableName UTF8String], [variableValue UTF8String], true);
832 }
833 
834 + (void)ignoreSignal:(Signal)signal {
835  if (signal == SignalQuit) {
836  handleSIGQUIT = 0;
837  } else if (signal == SignalInt) {
838  handleSIGINT = 0;
839  } else if (signal == SignalTerm) {
840  handleSIGTERM = 0;
841  } else if (signal == SignalXcpu) {
842  handleSIGXCPU = 0;
843  } else if (signal == SignalPipe) {
844  handleSIGPIPE = 0;
845  }
846 }
847 
848 + (void)ffmpegExecute:(FFmpegSession*)ffmpegSession {
849  [FFmpegKitConfig addSession:ffmpegSession];
850  [ffmpegSession startRunning];
851 
852  @try {
853  int returnCode = executeFFmpeg([ffmpegSession getSessionId], [ffmpegSession getArguments]);
854  [ffmpegSession complete:[[ReturnCode alloc] init:returnCode]];
855  } @catch (NSException *exception) {
856  [ffmpegSession fail:exception];
857  NSLog(@"FFmpeg execute failed: %@.%@", [FFmpegKit argumentsToString:[ffmpegSession getArguments]], [NSString stringWithFormat:@"%@", [exception callStackSymbols]]);
858  }
859 }
860 
861 + (void)ffprobeExecute:(FFprobeSession*)ffprobeSession {
862  [FFmpegKitConfig addSession:ffprobeSession];
863  [ffprobeSession startRunning];
864 
865  @try {
866  int returnCode = executeFFprobe([ffprobeSession getSessionId], [ffprobeSession getArguments]);
867  [ffprobeSession complete:[[ReturnCode alloc] init:returnCode]];
868  } @catch (NSException *exception) {
869  [ffprobeSession fail:exception];
870  NSLog(@"FFprobe execute failed: %@.%@", [FFmpegKit argumentsToString:[ffprobeSession getArguments]], [NSString stringWithFormat:@"%@", [exception callStackSymbols]]);
871  }
872 }
873 
874 + (void)getMediaInformationExecute:(MediaInformationSession*)mediaInformationSession withTimeout:(int)waitTimeout {
875  [FFmpegKitConfig addSession:mediaInformationSession];
876  [mediaInformationSession startRunning];
877 
878  @try {
879  int returnCodeValue = executeFFprobe([mediaInformationSession getSessionId], [mediaInformationSession getArguments]);
880  ReturnCode* returnCode = [[ReturnCode alloc] init:returnCodeValue];
881  [mediaInformationSession complete:returnCode];
882  if ([returnCode isSuccess]) {
883  MediaInformation* mediaInformation = [MediaInformationJsonParser from:[mediaInformationSession getAllLogsAsStringWithTimeout:waitTimeout]];
884  [mediaInformationSession setMediaInformation:mediaInformation];
885  }
886  } @catch (NSException *exception) {
887  [mediaInformationSession fail:exception];
888  NSLog(@"Get media information execute failed: %@.%@", [FFmpegKit argumentsToString:[mediaInformationSession getArguments]], [NSString stringWithFormat:@"%@", [exception callStackSymbols]]);
889  }
890 }
891 
892 + (void)asyncFFmpegExecute:(FFmpegSession*)ffmpegSession {
893  [FFmpegKitConfig asyncFFmpegExecute:ffmpegSession onDispatchQueue:asyncDispatchQueue];
894 }
895 
896 + (void)asyncFFmpegExecute:(FFmpegSession*)ffmpegSession onDispatchQueue:(dispatch_queue_t)queue {
897  [FFmpegKitConfig addSession:ffmpegSession];
898 
899  dispatch_async(queue, ^{
900  [FFmpegKitConfig ffmpegExecute:ffmpegSession];
901  ExecuteCallback globalExecuteCallback = [FFmpegKitConfig getExecuteCallback];
902  if (globalExecuteCallback != nil) {
903  globalExecuteCallback(ffmpegSession);
904  }
905 
906  ExecuteCallback sessionExecuteCallback = [ffmpegSession getExecuteCallback];
907  if (sessionExecuteCallback != nil) {
908  sessionExecuteCallback(ffmpegSession);
909  }
910  });
911 }
912 
913 + (void)asyncFFprobeExecute:(FFprobeSession*)ffprobeSession {
914  [FFmpegKitConfig asyncFFprobeExecute:ffprobeSession onDispatchQueue:asyncDispatchQueue];
915 }
916 
917 + (void)asyncFFprobeExecute:(FFprobeSession*)ffprobeSession onDispatchQueue:(dispatch_queue_t)queue {
918  [FFmpegKitConfig addSession:ffprobeSession];
919 
920  dispatch_async(queue, ^{
921  [FFmpegKitConfig ffprobeExecute:ffprobeSession];
922  ExecuteCallback globalExecuteCallback = [FFmpegKitConfig getExecuteCallback];
923  if (globalExecuteCallback != nil) {
924  globalExecuteCallback(ffprobeSession);
925  }
926 
927  ExecuteCallback sessionExecuteCallback = [ffprobeSession getExecuteCallback];
928  if (sessionExecuteCallback != nil) {
929  sessionExecuteCallback(ffprobeSession);
930  }
931  });
932 }
933 
934 + (void)asyncGetMediaInformationExecute:(MediaInformationSession*)mediaInformationSession withTimeout:(int)waitTimeout {
935  [FFmpegKitConfig asyncGetMediaInformationExecute:mediaInformationSession onDispatchQueue:asyncDispatchQueue withTimeout:waitTimeout];
936 }
937 
938 + (void)asyncGetMediaInformationExecute:(MediaInformationSession*)mediaInformationSession onDispatchQueue:(dispatch_queue_t)queue withTimeout:(int)waitTimeout {
939  [FFmpegKitConfig addSession:mediaInformationSession];
940 
941  dispatch_async(queue, ^{
942  [FFmpegKitConfig getMediaInformationExecute:mediaInformationSession withTimeout:waitTimeout];
943  ExecuteCallback globalExecuteCallback = [FFmpegKitConfig getExecuteCallback];
944  if (globalExecuteCallback != nil) {
945  globalExecuteCallback(mediaInformationSession);
946  }
947 
948  ExecuteCallback sessionExecuteCallback = [mediaInformationSession getExecuteCallback];
949  if (sessionExecuteCallback != nil) {
950  sessionExecuteCallback(mediaInformationSession);
951  }
952  });
953 }
954 
955 + (void)enableLogCallback:(LogCallback)callback {
956  logCallback = callback;
957 }
958 
959 + (void)enableStatisticsCallback:(StatisticsCallback)callback {
960  statisticsCallback = callback;
961 }
962 
963 + (void)enableExecuteCallback:(ExecuteCallback)callback {
964  executeCallback = callback;
965 }
966 
968  return executeCallback;
969 }
970 
971 + (int)getLogLevel {
972  return configuredLogLevel;
973 }
974 
975 + (void)setLogLevel:(int)level {
976  configuredLogLevel = level;
977 }
978 
979 + (NSString*)logLevelToString:(int)level {
980  switch (level) {
981  case LevelAVLogStdErr: return @"STDERR";
982  case LevelAVLogTrace: return @"TRACE";
983  case LevelAVLogDebug: return @"DEBUG";
984  case LevelAVLogVerbose: return @"VERBOSE";
985  case LevelAVLogInfo: return @"INFO";
986  case LevelAVLogWarning: return @"WARNING";
987  case LevelAVLogError: return @"ERROR";
988  case LevelAVLogFatal: return @"FATAL";
989  case LevelAVLogPanic: return @"PANIC";
990  case LevelAVLogQuiet: return @"QUIET";
991  default: return @"";
992  }
993 }
994 
996  return sessionHistorySize;
997 }
998 
999 + (void)setSessionHistorySize:(int)pSessionHistorySize {
1000  if (pSessionHistorySize >= SESSION_MAP_SIZE) {
1001 
1002  /*
1003  * THERE IS A HARD LIMIT ON THE NATIVE SIDE. HISTORY SIZE MUST BE SMALLER THAN SESSION_MAP_SIZE
1004  */
1005  @throw([NSException exceptionWithName:NSInvalidArgumentException reason:@"Session history size must not exceed the hard limit!" userInfo:nil]);
1006  } else if (pSessionHistorySize > 0) {
1007  sessionHistorySize = pSessionHistorySize;
1008  }
1009 }
1010 
1011 + (void)addSession:(id<Session>)session {
1012  NSNumber* sessionIdNumber = [NSNumber numberWithLong:[session getSessionId]];
1013 
1014  [sessionHistoryLock lock];
1015 
1016  /*
1017  * ASYNC SESSIONS CALL THIS METHOD TWICE
1018  * THIS CHECK PREVENTS ADDING THE SAME SESSION TWICE
1019  */
1020  if ([sessionHistoryMap objectForKey:sessionIdNumber] == nil) {
1021  [sessionHistoryMap setObject:session forKey:sessionIdNumber];
1022  [sessionHistoryList addObject:session];
1023  if ([sessionHistoryList count] > sessionHistorySize) {
1024  id<Session> first = [sessionHistoryList firstObject];
1025  if (first != nil) {
1026  NSNumber* key = [NSNumber numberWithLong:[first getSessionId]];
1027  [sessionHistoryList removeObject:key];
1028  [sessionHistoryMap removeObjectForKey:key];
1029  }
1030  }
1031  }
1032 
1033  [sessionHistoryLock unlock];
1034 }
1035 
1036 + (id<Session>)getSession:(long)sessionId {
1037  [sessionHistoryLock lock];
1038 
1039  id<Session> session = [sessionHistoryMap objectForKey:[NSNumber numberWithLong:sessionId]];
1040 
1041  [sessionHistoryLock unlock];
1042 
1043  return session;
1044 }
1045 
1046 + (id<Session>)getLastSession {
1047  [sessionHistoryLock lock];
1048 
1049  id<Session> lastSession = [sessionHistoryList lastObject];
1050 
1051  [sessionHistoryLock unlock];
1052 
1053  return lastSession;
1054 }
1055 
1057  id<Session> lastCompletedSession = nil;
1058 
1059  [sessionHistoryLock lock];
1060 
1061  for(int i = [sessionHistoryList count] - 1; i >= 0; i--) {
1062  id<Session> session = [sessionHistoryList objectAtIndex:i];
1063  if ([session getState] == SessionStateCompleted) {
1064  lastCompletedSession = session;
1065  break;
1066  }
1067  }
1068 
1069  [sessionHistoryLock unlock];
1070 
1071  return lastCompletedSession;
1072 }
1073 
1074 + (NSArray*)getSessions {
1075  [sessionHistoryLock lock];
1076 
1077  NSArray* sessionsCopy = [sessionHistoryList copy];
1078 
1079  [sessionHistoryLock unlock];
1080 
1081  return sessionsCopy;
1082 }
1083 
1084 + (NSArray*)getFFmpegSessions {
1085  NSMutableArray* ffmpegSessions = [[NSMutableArray alloc] init];
1086 
1087  [sessionHistoryLock lock];
1088 
1089  for(int i = 0; i < [sessionHistoryList count]; i++) {
1090  id<Session> session = [sessionHistoryList objectAtIndex:i];
1091  if ([session isFFmpeg]) {
1092  [ffmpegSessions addObject:session];
1093  }
1094  }
1095 
1096  [sessionHistoryLock unlock];
1097 
1098  return ffmpegSessions;
1099 }
1100 
1101 + (NSArray*)getFFprobeSessions {
1102  NSMutableArray* ffprobeSessions = [[NSMutableArray alloc] init];
1103 
1104  [sessionHistoryLock lock];
1105 
1106  for(int i = 0; i < [sessionHistoryList count]; i++) {
1107  id<Session> session = [sessionHistoryList objectAtIndex:i];
1108  if ([session isFFprobe]) {
1109  [ffprobeSessions addObject:session];
1110  }
1111  }
1112 
1113  [sessionHistoryLock unlock];
1114 
1115  return ffprobeSessions;
1116 }
1117 
1118 + (NSArray*)getSessionsByState:(SessionState)state {
1119  NSMutableArray* sessions = [[NSMutableArray alloc] init];
1120 
1121  [sessionHistoryLock lock];
1122 
1123  for(int i = 0; i < [sessionHistoryList count]; i++) {
1124  id<Session> session = [sessionHistoryList objectAtIndex:i];
1125  if ([session getState] == state) {
1126  [sessions addObject:session];
1127  }
1128  }
1129 
1130  [sessionHistoryLock unlock];
1131 
1132  return sessions;
1133 }
1134 
1135 + (LogRedirectionStrategy)getLogRedirectionStrategy {
1137 }
1138 
1139 + (void)setLogRedirectionStrategy:(LogRedirectionStrategy)logRedirectionStrategy {
1140  globalLogRedirectionStrategy = logRedirectionStrategy;
1141 }
1142 
1143 + (int)messagesInTransmit:(long)sessionId {
1144  return atomic_load(&sessionInTransitMessageCountMap[sessionId % SESSION_MAP_SIZE]);
1145 }
1146 
1147 + (NSString*)sessionStateToString:(SessionState)state {
1148  switch (state) {
1149  case SessionStateCreated: return @"CREATED";
1150  case SessionStateRunning: return @"RUNNING";
1151  case SessionStateFailed: return @"FAILED";
1152  case SessionStateCompleted: return @"COMPLETED";
1153  default: return @"";
1154  }
1155 }
1156 
1157 @end
void(^ ExecuteCallback)(id< Session > session)
int executeFFprobe(long sessionId, NSArray *arguments)
void callbackWait(int milliSeconds)
void ffmpegkit_log_callback_function(void *ptr, int level, const char *format, va_list vargs)
static atomic_short sessionMap[SESSION_MAP_SIZE]
float _statisticsQuality
void cancelSession(long sessionId)
static volatile NSMutableDictionary * sessionHistoryMap
int _logLevel
static int sessionHistorySize
static NSRecursiveLock * lock
int _statisticsTime
volatile int handleSIGINT
int ffprobe_execute(int argc, char **argv)
volatile int handleSIGTERM
void process_log(long sessionId, int levelValue, NSString *logMessage)
void ffmpegkit_statistics_callback_function(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed)
static dispatch_queue_t asyncDispatchQueue
void removeSession(long sessionId)
void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed)
static LogCallback logCallback
void addSession(long sessionId)
void callbackNotify()
float _statisticsFps
CallbackData * callbackDataRemove()
__thread volatile long _sessionId
void callbackBlockFunction()
double _statisticsBitrate
double _statisticsSpeed
static NSRecursiveLock * sessionHistoryLock
static ExecuteCallback executeCallback
static dispatch_semaphore_t semaphore
NSString * _logData
static int redirectionEnabled
void resetMessagesInTransmit(long sessionId)
static NSMutableArray * callbackDataArray
int64_t _statisticsSize
int _statisticsFrameNumber
void process_statistics(long sessionId, int videoFrameNumber, float videoFps, float videoQuality, long size, int time, double bitrate, double speed)
int cancelRequested(long sessionId)
volatile int handleSIGPIPE
const int SESSION_MAP_SIZE
NSString *const FFmpegKitVersion
void logCallbackDataAdd(int level, NSString *logData)
volatile int handleSIGXCPU
static LogRedirectionStrategy globalLogRedirectionStrategy
static StatisticsCallback statisticsCallback
static atomic_int sessionInTransitMessageCountMap[SESSION_MAP_SIZE]
static AtomicLong * pipeIndexGenerator
static NSMutableArray * sessionHistoryList
typedef NS_ENUM(NSUInteger, CallbackType)
NSString *const FFmpegKitNamedPipePrefix
int configuredLogLevel
int ffmpeg_execute(int argc, char **argv)
volatile int handleSIGQUIT
int executeFFmpeg(long sessionId, NSArray *arguments)
void(^ LogCallback)(Log *log)
Definition: LogCallback.h:31
void(^ StatisticsCallback)(Statistics *statistics)
void set_report_callback(void(*callback)(int, float, float, int64_t, int, double, double))
void fail:(NSException *exception)
void complete:(ReturnCode *returnCode)
ExecuteCallback getExecuteCallback()
NSString * getAllLogsAsStringWithTimeout:(int waitTimeout)
long getAndIncrement()
Definition: AtomicLong.m:40
void addSession:(id< Session > session)
void asyncFFmpegExecute:onDispatchQueue:(FFmpegSession *ffmpegSession,[onDispatchQueue] dispatch_queue_t queue)
NSArray * getFFmpegSessions()
LogRedirectionStrategy getLogRedirectionStrategy()
void getMediaInformationExecute:withTimeout:(MediaInformationSession *mediaInformationSession,[withTimeout] int waitTimeout)
NSString * registerNewFFmpegPipe()
NSArray * getSessions()
NSString * getVersion()
int setFontconfigConfigurationPath:(NSString *path)
ExecuteCallback getExecuteCallback()
NSString * getBuildDate()
void ffprobeExecute:(FFprobeSession *ffprobeSession)
NSArray * getFFprobeSessions()
void ffmpegExecute:(FFmpegSession *ffmpegSession)
id< Session > getSession:(long sessionId)
int setEnvironmentVariable:value:(NSString *variableName,[value] NSString *variableValue)
id< Session > getLastSession()
void asyncFFprobeExecute:onDispatchQueue:(FFprobeSession *ffprobeSession,[onDispatchQueue] dispatch_queue_t queue)
NSString * getFFmpegVersion()
void asyncGetMediaInformationExecute:onDispatchQueue:withTimeout:(MediaInformationSession *mediaInformationSession,[onDispatchQueue] dispatch_queue_t queue,[withTimeout] int waitTimeout)
void setFontDirectoryList:with:(NSArray *fontDirectoryList,[with] NSDictionary *fontNameMapping)
void closeFFmpegPipe:(NSString *ffmpegPipePath)
id< Session > getLastCompletedSession()
StatisticsCallback getStatisticsCallback()
Definition: FFmpegSession.m:88
void addStatistics:(Statistics *statistics)
Definition: Log.h:29
MediaInformation * from:(NSString *ffprobeJsonOutput)
void setMediaInformation:(MediaInformation *mediaInformation)
long getSessionId()