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