Skip to content

Using FFmpegKitNext on tvOS

Taner Sener edited this page Jul 29, 2026 · 2 revisions

This page shows how to use FFmpegKitNext in a tvOS application after the tvOS framework, xcframework or local Swift package has already been added to your Xcode project.

It covers the tvOS Objective-C and Swift API usage.

1. Import the API

Objective-C projects import headers from the ffmpegkit module:

#include <ffmpegkit/FFmpegKit.h>
#include <ffmpegkit/FFmpegKitConfig.h>
#include <ffmpegkit/FFprobeKit.h>

Swift projects import the same module:

import ffmpegkit

2. Execute FFmpeg

2.1 Synchronous execution

#include <ffmpegkit/FFmpegKit.h>
#include <ffmpegkit/FFmpegKitConfig.h>

FFmpegSession *session = [FFmpegKit execute:@"-i file1.mp4 -c:v mpeg4 file2.mp4"];
ReturnCode *returnCode = [session getReturnCode];

if ([ReturnCode isSuccess:returnCode]) {
    // Success.
} else if ([ReturnCode isCancel:returnCode]) {
    // Cancelled.
} else {
    NSLog(@"Command failed with state %@ and rc %@.%@",
          [FFmpegKitConfig sessionStateToString:[session getState]],
          returnCode,
          [session getFailStackTrace]);
}

2.2 Argument-array execution

String commands are split by spaces. Use executeWithArguments: when paths or filter arguments may contain spaces or quotes.

NSArray *arguments = @[
    @"-i", inputPath,
    @"-vf", @"scale=1280:-2",
    @"-c:v", @"mpeg4",
    outputPath
];

FFmpegSession *session = [FFmpegKit executeWithArguments:arguments];

2.3 Asynchronous execution

FFmpegSession *session =
    [FFmpegKit executeAsync:@"-i file1.mp4 -c:v mpeg4 file2.mp4"
        withCompleteCallback:^(FFmpegSession *session) {
            SessionState state = [session getState];
            ReturnCode *returnCode = [session getReturnCode];

            NSLog(@"FFmpeg exited with state %@ and rc %@.%@",
                  [FFmpegKitConfig sessionStateToString:state],
                  returnCode,
                  [session getFailStackTrace]);
        }
        withLogCallback:^(Log *log) {
            NSLog(@"%@", [log getMessage]);
        }
        withStatisticsCallback:^(Statistics *statistics) {
            NSLog(@"frame=%d time=%f speed=%f",
                  [statistics getVideoFrameNumber],
                  [statistics getTime],
                  [statistics getSpeed]);
        }];

2.4 Run on a dispatch queue

dispatch_queue_t queue =
    dispatch_queue_create("com.example.ffmpeg", DISPATCH_QUEUE_SERIAL);

[FFmpegKit executeWithArgumentsAsync:arguments
                withCompleteCallback:^(FFmpegSession *session) {
                    NSLog(@"Completed session %ld", [session getSessionId]);
                }
                     withLogCallback:nil
              withStatisticsCallback:nil
                      onDispatchQueue:queue];

3. Inspect sessions

Each execute call creates a session.

FFmpegSession *session = [FFmpegKit execute:@"-i file1.mp4 -c:v mpeg4 file2.mp4"];

long sessionId = [session getSessionId];
NSString *command = [session getCommand];
NSArray *arguments = [session getArguments];
SessionState state = [session getState];
ReturnCode *returnCode = [session getReturnCode];

NSDate *startTime = [session getStartTime];
NSDate *endTime = [session getEndTime];
long duration = [session getDuration];

NSString *output = [session getOutput];
NSString *failStackTrace = [session getFailStackTrace];
NSArray *logs = [session getLogs];
NSArray *allLogs = [session getAllLogs];
NSArray *statistics = [session getStatistics];
Statistics *lastStatistics = [session getLastReceivedStatistics];

Session history can be queried and bounded:

[FFmpegKitConfig setSessionHistorySize:100];

NSArray *sessions = [FFmpegKitConfig getSessions];
NSArray *ffmpegSessions = [FFmpegKitConfig getFFmpegSessions];
NSArray *runningSessions = [FFmpegKitConfig getSessionsByState:SessionStateRunning];

id<Session> lastSession = [FFmpegKitConfig getLastSession];
id<Session> lastCompletedSession = [FFmpegKitConfig getLastCompletedSession];

[FFmpegKitConfig deleteSession:sessionId];

4. Execute FFprobe and read media information

4.1 FFprobe command execution

#include <ffmpegkit/FFprobeKit.h>

NSString *ffprobeCommand =
    [NSString stringWithFormat:@"-hide_banner -v error -show_format -show_streams %@", inputPath];

FFprobeSession *session = [FFprobeKit execute:ffprobeCommand];

if (![ReturnCode isSuccess:[session getReturnCode]]) {
    NSLog(@"FFprobe failed. Output: %@", [session getOutput]);
}

Asynchronous execution:

[FFprobeKit executeAsync:ffprobeCommand
    withCompleteCallback:^(FFprobeSession *session) {
        NSLog(@"FFprobe output: %@", [session getOutput]);
    }];

For paths that may contain spaces, use arguments:

NSArray *arguments = @[
    @"-hide_banner",
    @"-v", @"error",
    @"-show_format",
    @"-show_streams",
    inputPath
];

FFprobeSession *session = [FFprobeKit executeWithArguments:arguments];

4.2 Media information

MediaInformationSession *session = [FFprobeKit getMediaInformation:inputPath];

if ([ReturnCode isSuccess:[session getReturnCode]]) {
    MediaInformation *mediaInformation = [session getMediaInformation];
    NSLog(@"format=%@ duration=%@ size=%@",
          [mediaInformation getFormat],
          [mediaInformation getDuration],
          [mediaInformation getSize]);

    for (StreamInformation *stream in [mediaInformation getStreams]) {
        NSLog(@"stream=%@ codec=%@ width=%@ height=%@",
              [stream getType],
              [stream getCodec],
              [stream getWidth],
              [stream getHeight]);
    }
} else {
    NSLog(@"Media information failed. Output: %@", [session getOutput]);
}

Asynchronous media information:

[FFprobeKit getMediaInformationAsync:inputPath
                 withCompleteCallback:^(MediaInformationSession *session) {
                     MediaInformation *mediaInformation = [session getMediaInformation];
                     NSLog(@"duration=%@", [mediaInformation getDuration]);
                 }];

Use the command variants when you need a custom FFprobe command. The command must print JSON media information.

NSArray *arguments = @[
    @"-v", @"error",
    @"-print_format", @"json",
    @"-show_format",
    @"-show_streams",
    inputPath
];

[FFprobeKit getMediaInformationFromCommandArgumentsAsync:arguments
                                    withCompleteCallback:^(MediaInformationSession *session) {
                                        MediaInformation *info = [session getMediaInformation];
                                        NSLog(@"format=%@", [info getFormat]);
                                    }
                                         withLogCallback:nil
                                         onDispatchQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)
                                             withTimeout:10000];

5. Cancel executions

// Cancel all running FFmpeg sessions.
[FFmpegKit cancel];

// Cancel one session.
[FFmpegKit cancel:sessionId];

6. Global callbacks and configuration

[FFmpegKitConfig enableFFmpegSessionCompleteCallback:^(FFmpegSession *session) {
    NSLog(@"Global FFmpeg callback: %ld", [session getSessionId]);
}];

[FFmpegKitConfig enableFFprobeSessionCompleteCallback:^(FFprobeSession *session) {
    NSLog(@"Global FFprobe callback: %ld", [session getSessionId]);
}];

[FFmpegKitConfig enableMediaInformationSessionCompleteCallback:^(MediaInformationSession *session) {
    NSLog(@"Global media information callback: %ld", [session getSessionId]);
}];

[FFmpegKitConfig enableLogCallback:^(Log *log) {
    NSLog(@"%@", [log getMessage]);
}];

[FFmpegKitConfig enableStatisticsCallback:^(Statistics *statistics) {
    NSLog(@"time=%f bitrate=%f", [statistics getTime], [statistics getBitrate]);
}];

[FFmpegKitConfig setLogLevel:LevelAVLogInfo];

Redirection is enabled by default. If you call [FFmpegKitConfig disableRedirection], logs go to stderr, callbacks are disabled and FFprobeKit media information helpers do not work.

For advanced cases, create a session yourself and choose a log redirection strategy:

FFmpegSession *session =
    [FFmpegSession create:arguments
     withCompleteCallback:^(FFmpegSession *session) {
         NSLog(@"Completed with rc %@", [session getReturnCode]);
     }
          withLogCallback:nil
   withStatisticsCallback:nil
withLogRedirectionStrategy:LogRedirectionStrategyNeverPrintLogs];

[FFmpegKitConfig asyncFFmpegExecute:session];

7. Fonts and drawtext

drawtext requires a tvOS artifact that includes freetype and harfbuzz. If you want font family lookup through registered font directories, the artifact must also include fontconfig.

Register application fonts before running commands that need them:

NSString *resourceFolder = [[NSBundle mainBundle] resourcePath];
NSDictionary *fontNameMapping = @{@"MyFontName": @"Doppio One"};

[FFmpegKitConfig setFontDirectoryList:@[
    resourceFolder,
    @"/System/Library/Fonts"
] with:fontNameMapping];

You can then use the mapped name in filters that use fontconfig:

NSString *command =
    @"-i input.mp4 -vf drawtext=font='MyFontName':text='Hello':x=20:y=20 output.mp4";

8. FFmpegKit protocols

Default FFmpegKitNext tvOS artifacts support the custom ffkitmem: and ffkitstream: protocols. If your artifact was built without FFmpegKit protocol support, these examples will not work.

8.1 Seekable in-memory input and output

Use FFmpegKitInputBuffer and FFmpegKitOutputBuffer for finite, seekable memory-backed input and output. This avoids temporary files for data that already lives in memory.

#include <ffmpegkit/FFmpegKitInputBuffer.h>
#include <ffmpegkit/FFmpegKitOutputBuffer.h>

NSData *inputData = [NSData dataWithContentsOfFile:inputImagePath];
FFmpegKitInputBuffer *input = [FFmpegKitInputBuffer fromData:inputData extension:@"jpg"];
FFmpegKitOutputBuffer *output = [FFmpegKitOutputBuffer create:@"jpg"];

NSArray *arguments = @[
    @"-hide_banner",
    @"-y",
    @"-i", [input getUrl],
    @"-vf", @"scale=640:-2",
    [output getUrl]
];

[FFmpegKit executeWithArgumentsAsync:arguments
                withCompleteCallback:^(FFmpegSession *session) {
                    if ([ReturnCode isSuccess:[session getReturnCode]]) {
                        NSData *result = [output toData];
                        NSLog(@"Created %ld bytes", (long)[result length]);
                    } else {
                        NSLog(@"FFmpeg failed: %@", [session getAllLogsAsString]);
                    }

                    [input close];
                    [output close];
                }];

asDataNoCopy returns an NSData view backed by native memory. That view is valid only until the output buffer is closed or FFmpeg writes more data to the same resource.

8.2 Non-seekable streaming input

Use FFmpegKitStreamInput when data arrives over time. Choose an FFmpeg demuxer that can read non-seekable input.

#include <ffmpegkit/FFmpegKitStreamInput.h>

FFmpegKitStreamInput *streamInput = [FFmpegKitStreamInput create:@"raw"];

NSArray *arguments = @[
    @"-f", @"s16le",
    @"-ar", @"44100",
    @"-ac", @"1",
    @"-i", [streamInput getUrl],
    @"-f", @"wav",
    outputPath
];

[FFmpegKit executeWithArgumentsAsync:arguments
                withCompleteCallback:^(FFmpegSession *session) {
                    NSLog(@"stream input rc=%@", [session getReturnCode]);
                    [streamInput close];
                }];

dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
    for (NSData *pcmChunk in pcmChunks) {
        [streamInput write:pcmChunk timeout:5000];
    }
    [streamInput closeInput];
});

8.3 Non-seekable streaming output

Use FFmpegKitStreamOutput when the application wants to consume output incrementally. Choose a muxer that can write to non-seekable output, such as mpegts.

#include <ffmpegkit/FFmpegKitStreamOutput.h>

FFmpegKitStreamOutput *streamOutput = [FFmpegKitStreamOutput create:@"ts"];

NSArray *arguments = @[
    @"-i", inputPath,
    @"-f", @"mpegts",
    [streamOutput getUrl]
];

[FFmpegKit executeWithArgumentsAsync:arguments
                withCompleteCallback:^(FFmpegSession *session) {
                    NSLog(@"stream output rc=%@", [session getReturnCode]);
                    [streamOutput close];
                }];

dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
    NSData *chunk = [streamOutput read:32768 timeout:1000];
    if ([chunk length] > 0) {
        // Process the first output chunk.
    }
});

9. Named pipes

Named pipes are available for workflows that need a filesystem path.

NSString *pipePath = [FFmpegKitConfig registerNewFFmpegPipe];

NSArray *arguments = @[
    @"-y",
    @"-f", @"image2pipe",
    @"-i", pipePath,
    @"-c:v", @"mpeg4",
    outputPath
];

[FFmpegKit executeWithArgumentsAsync:arguments
                withCompleteCallback:^(FFmpegSession *session) {
                    [FFmpegKitConfig closeFFmpegPipe:pipePath];
                    NSLog(@"pipe rc=%@", [session getReturnCode]);
                }];

dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
    NSFileHandle *writer = [NSFileHandle fileHandleForWritingAtPath:pipePath];
    [writer writeData:imageData];
    [writer closeFile];
});

10. Signals

Applications that embed Mono-based runtimes, such as Unity or Xamarin applications, should ignore SIGXCPU before running FFmpeg commands.

[FFmpegKitConfig ignoreSignal:SignalXcpu];

11. Test Application

See the tvOS test applications in the ffmpeg-kit-next-test project for full Objective-C and Swift examples, including command execution, HTTPS media information, pipes, concurrent execution and FFmpegKit protocols.

Clone this wiki locally