Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,87 @@
#import <FFMpegBinding/FFMpegAVCodec.h>
#import "libavcodec/avcodec.h"
@interface FFMpegAVCodec () {
AVCodec const *_impl;
}
@end
@implementation FFMpegAVCodec
- (instancetype)initWithImpl:(AVCodec const *)impl {
self = [super init];
if (self != nil) {
_impl = impl;
}
return self;
}
+ (FFMpegAVCodec * _Nullable)findForId:(int)codecId preferHardwareAccelerationCapable:(_Bool)preferHardwareAccelerationCapable {
if (preferHardwareAccelerationCapable && codecId == AV_CODEC_ID_AV1) {
void *codecIterationState = nil;
while (true) {
AVCodec const *codec = av_codec_iterate(&codecIterationState);
if (!codec) {
break;
}
if (!av_codec_is_decoder(codec)) {
continue;
}
if (codec->id != codecId) {
continue;
}
if (strncmp(codec->name, "av1", 2) == 0) {
return [[FFMpegAVCodec alloc] initWithImpl:codec];
}
}
} else if (preferHardwareAccelerationCapable && codecId == AV_CODEC_ID_H264) {
void *codecIterationState = nil;
while (true) {
AVCodec const *codec = av_codec_iterate(&codecIterationState);
if (!codec) {
break;
}
if (!av_codec_is_decoder(codec)) {
continue;
}
if (codec->id != codecId) {
continue;
}
if (strncmp(codec->name, "h264", 2) == 0) {
return [[FFMpegAVCodec alloc] initWithImpl:codec];
}
}
} else if (preferHardwareAccelerationCapable && codecId == AV_CODEC_ID_HEVC) {
void *codecIterationState = nil;
while (true) {
AVCodec const *codec = av_codec_iterate(&codecIterationState);
if (!codec) {
break;
}
if (!av_codec_is_decoder(codec)) {
continue;
}
if (codec->id != codecId) {
continue;
}
if (strncmp(codec->name, "hevc", 2) == 0) {
return [[FFMpegAVCodec alloc] initWithImpl:codec];
}
}
}
AVCodec const *codec = avcodec_find_decoder(codecId);
if (codec) {
return [[FFMpegAVCodec alloc] initWithImpl:codec];
} else {
return nil;
}
}
- (void *)impl {
return (void *)_impl;
}
@end
@@ -0,0 +1,88 @@
#import <FFMpegBinding/FFMpegAVCodecContext.h>
#import <FFMpegBinding/FFMpegAVFrame.h>
#import <FFMpegBinding/FFMpegAVCodec.h>
#import "libavformat/avformat.h"
#import "libavcodec/avcodec.h"
static enum AVPixelFormat getPreferredPixelFormat(__unused AVCodecContext *ctx, __unused const enum AVPixelFormat *pix_fmts) {
return AV_PIX_FMT_VIDEOTOOLBOX;
}
@interface FFMpegAVCodecContext () {
FFMpegAVCodec *_codec;
AVCodecContext *_impl;
}
@end
@implementation FFMpegAVCodecContext
- (instancetype)initWithCodec:(FFMpegAVCodec *)codec {
self = [super init];
if (self != nil) {
_codec = codec;
_impl = avcodec_alloc_context3((AVCodec *)[codec impl]);
_impl->max_pixels = 4 * 1024 * 4 * 1024;
}
return self;
}
- (void)dealloc {
if (_impl) {
avcodec_free_context(&_impl);
}
}
- (void *)impl {
return _impl;
}
- (int32_t)channels {
#if LIBAVFORMAT_VERSION_MAJOR >= 59
return (int32_t)_impl->ch_layout.nb_channels;
#else
return (int32_t)_impl->channels;
#endif
}
- (int32_t)sampleRate {
return (int32_t)_impl->sample_rate;
}
- (FFMpegAVSampleFormat)sampleFormat {
return (FFMpegAVSampleFormat)_impl->sample_fmt;
}
- (bool)open {
int result = avcodec_open2(_impl, (AVCodec *)[_codec impl], nil);
return result >= 0;
}
- (bool)sendEnd {
int status = avcodec_send_packet(_impl, nil);
return status == 0;
}
- (void)setupHardwareAccelerationIfPossible {
av_hwdevice_ctx_create(&_impl->hw_device_ctx, AV_HWDEVICE_TYPE_VIDEOTOOLBOX, nil, nil, 0);
_impl->get_format = getPreferredPixelFormat;
}
- (FFMpegAVCodecContextReceiveResult)receiveIntoFrame:(FFMpegAVFrame *)frame {
int status = avcodec_receive_frame(_impl, (AVFrame *)[frame impl]);
if (status == 0) {
return FFMpegAVCodecContextReceiveResultSuccess;
} else if (status == -35) {
return FFMpegAVCodecContextReceiveResultNotEnoughData;
} else {
return FFMpegAVCodecContextReceiveResultError;
}
}
- (void)flushBuffers {
avcodec_flush_buffers(_impl);
}
@end
@@ -0,0 +1,216 @@
#import <FFMpegBinding/FFMpegAVFormatContext.h>
#import <FFMpegBinding/FFMpegAVIOContext.h>
#import <FFMpegBinding/FFMpegPacket.h>
#import <FFMpegBinding/FFMpegAVCodecContext.h>
#import "libavcodec/avcodec.h"
#import "libavformat/avformat.h"
#import "libavutil/display.h"
int FFMpegCodecIdH264 = AV_CODEC_ID_H264;
int FFMpegCodecIdHEVC = AV_CODEC_ID_HEVC;
int FFMpegCodecIdMPEG4 = AV_CODEC_ID_MPEG4;
int FFMpegCodecIdVP9 = AV_CODEC_ID_VP9;
int FFMpegCodecIdVP8 = AV_CODEC_ID_VP8;
int FFMpegCodecIdAV1 = AV_CODEC_ID_AV1;
static int get_stream_rotation(const AVStream *stream) {
AVDictionaryEntry *e = av_dict_get (stream->metadata, "rotate", NULL, 0);
if (e && e->value) {
if (!strcmp (e->value, "90") || !strcmp (e->value, "-270")) {
return 90;
} else if (!strcmp (e->value, "270") || !strcmp (e->value, "-90")) {
return 270;
} else if (!strcmp (e->value, "180") || !strcmp (e->value, "-180")) {
return 180;
} else if (!strcmp (e->value, "0")) {
return 0;
}
}
const AVPacketSideData *displaymatrix = av_packet_side_data_get(stream->codecpar->coded_side_data, stream->codecpar->nb_coded_side_data, AV_PKT_DATA_DISPLAYMATRIX);
if (displaymatrix) {
return ((int)-av_display_rotation_get((int32_t *)displaymatrix->data) + 360) % 360;
}
return 0;
}
@interface FFMpegAVFormatContext () {
AVFormatContext *_impl;
}
@end
@implementation FFMpegAVFormatContext
- (instancetype)init {
self = [super init];
if (self != nil) {
_impl = avformat_alloc_context();
}
return self;
}
- (void)dealloc {
if (_impl != nil) {
avformat_close_input(&_impl);
}
}
- (void)setIOContext:(FFMpegAVIOContext *)ioContext {
_impl->pb = [ioContext impl];
}
- (bool)openInputWithDirectFilePath:(NSString * _Nullable)directFilePath {
AVDictionary *options = nil;
av_dict_set(&options, "usetoc", "1", 0);
const char *url = "file";
if (directFilePath) {
url = [directFilePath UTF8String];
}
int result = avformat_open_input(&_impl, url, nil, &options);
av_dict_free(&options);
if (_impl != nil) {
_impl->flags |= AVFMT_FLAG_FAST_SEEK;
_impl->flags |= AVFMT_FLAG_NOBUFFER;
}
return result >= 0;
}
- (bool)findStreamInfo {
int result = avformat_find_stream_info(_impl, nil);
return result >= 0;
}
- (void)seekFrameForStreamIndex:(int32_t)streamIndex pts:(int64_t)pts positionOnKeyframe:(bool)positionOnKeyframe {
int options = AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD;
if (!positionOnKeyframe) {
options |= AVSEEK_FLAG_ANY;
}
av_seek_frame(_impl, streamIndex, pts, options);
}
- (void)seekFrameForStreamIndex:(int32_t)streamIndex byteOffset:(int64_t)byteOffset {
int options = AVSEEK_FLAG_BYTE;
av_seek_frame(_impl, streamIndex, byteOffset, options);
}
- (bool)readFrameIntoPacket:(FFMpegPacket *)packet {
[packet reuse];
int result = av_read_frame(_impl, (AVPacket *)[packet impl]);
return result >= 0;
}
- (NSArray<NSNumber *> *)streamIndicesForType:(FFMpegAVFormatStreamType)type {
NSMutableArray<NSNumber *> *indices = [[NSMutableArray alloc] init];
enum AVMediaType mediaType;
switch(type) {
case FFMpegAVFormatStreamTypeAudio:
mediaType = AVMEDIA_TYPE_AUDIO;
break;
case FFMpegAVFormatStreamTypeVideo:
mediaType = AVMEDIA_TYPE_VIDEO;
break;
default:
mediaType = AVMEDIA_TYPE_VIDEO;
break;
}
for (unsigned int i = 0; i < _impl->nb_streams; i++) {
if (mediaType == _impl->streams[i]->codecpar->codec_type) {
[indices addObject:@(i)];
}
}
return indices;
}
- (bool)isAttachedPicAtStreamIndex:(int32_t)streamIndex {
return ((_impl->streams[streamIndex]->disposition) & AV_DISPOSITION_ATTACHED_PIC) != 0;
}
- (int)codecIdAtStreamIndex:(int32_t)streamIndex {
return _impl->streams[streamIndex]->codecpar->codec_id;
}
- (double)duration {
return (double)_impl->duration / AV_TIME_BASE;
}
- (int64_t)startTimeAtStreamIndex:(int32_t)streamIndex {
return _impl->streams[streamIndex]->start_time;
}
- (int64_t)durationAtStreamIndex:(int32_t)streamIndex {
return _impl->streams[streamIndex]->duration;
}
- (int)numberOfIndexEntriesAtStreamIndex:(int32_t)streamIndex {
return avformat_index_get_entries_count(_impl->streams[streamIndex]);
}
- (bool)fillIndexEntryAtStreamIndex:(int32_t)streamIndex entryIndex:(int32_t)entryIndex outEntry:(FFMpegAVIndexEntry * _Nonnull)outEntry {
const AVIndexEntry *entry = avformat_index_get_entry(_impl->streams[streamIndex], entryIndex);
if (!entry) {
outEntry->pos = -1;
outEntry->timestamp = 0;
outEntry->isKeyframe = false;
outEntry->size = 0;
return false;
}
outEntry->pos = entry->pos;
outEntry->timestamp = entry->timestamp;
outEntry->isKeyframe = (entry->flags & AVINDEX_KEYFRAME) != 0;
outEntry->size = entry->size;
return true;
}
- (bool)codecParamsAtStreamIndex:(int32_t)streamIndex toContext:(FFMpegAVCodecContext *)context {
int result = avcodec_parameters_to_context((AVCodecContext *)[context impl], _impl->streams[streamIndex]->codecpar);
return result >= 0;
}
- (FFMpegFpsAndTimebase)fpsAndTimebaseForStreamIndex:(int32_t)streamIndex defaultTimeBase:(CMTime)defaultTimeBase {
CMTime timebase;
CMTime fps;
AVStream *stream = _impl->streams[streamIndex];
if (stream->time_base.den != 0 && stream->time_base.num != 0) {
timebase = CMTimeMake((int64_t)stream->time_base.num, stream->time_base.den);
}/* else if (stream->codec->time_base.den != 0 && stream->codec->time_base.num != 0) {
timebase = CMTimeMake((int64_t)stream->codec->time_base.num, stream->codec->time_base.den);
}*/ else {
timebase = defaultTimeBase;
}
if (stream->avg_frame_rate.den != 0 && stream->avg_frame_rate.num != 0) {
fps = CMTimeMake((int64_t)stream->avg_frame_rate.num, stream->avg_frame_rate.den);
} else if (stream->r_frame_rate.den != 0 && stream->r_frame_rate.num != 0) {
fps = CMTimeMake((int64_t)stream->r_frame_rate.num, stream->r_frame_rate.den);
} else {
fps = CMTimeMake(1, 24);
}
return (FFMpegFpsAndTimebase){ .fps = fps, .timebase = timebase };
}
- (FFMpegStreamMetrics)metricsForStreamAtIndex:(int32_t)streamIndex {
int angleDegrees = get_stream_rotation(_impl->streams[streamIndex]);
double rotationAngle = 0.0;
rotationAngle = ((double)angleDegrees) * M_PI / 180.0;
return (FFMpegStreamMetrics){ .width = _impl->streams[streamIndex]->codecpar->width, .height = _impl->streams[streamIndex]->codecpar->height, .rotationAngle = rotationAngle, .extradata = _impl->streams[streamIndex]->codecpar->extradata, .extradataSize = _impl->streams[streamIndex]->codecpar->extradata_size };
}
- (void)forceVideoCodecId:(int)videoCodecId {
_impl->video_codec_id = videoCodecId;
_impl->video_codec = avcodec_find_decoder(videoCodecId);
}
@end
@@ -0,0 +1,109 @@
#import <FFMpegBinding/FFMpegAVFrame.h>
#import "libavformat/avformat.h"
@interface FFMpegAVFrame () {
AVFrame *_impl;
}
@end
@implementation FFMpegAVFrame
- (instancetype)init {
self = [super init];
if (self != nil) {
_impl = av_frame_alloc();
}
return self;
}
-(instancetype)initWithPixelFormat:(FFMpegAVFramePixelFormat)pixelFormat width:(int32_t)width height:(int32_t)height {
self = [super init];
if (self != nil) {
_impl = av_frame_alloc();
switch (pixelFormat) {
case FFMpegAVFramePixelFormatYUV:
_impl->format = AV_PIX_FMT_YUV420P;
break;
case FFMpegAVFramePixelFormatYUVA:
_impl->format = AV_PIX_FMT_YUVA420P;
break;
}
_impl->width = width;
_impl->height = height;
av_frame_get_buffer(_impl, 0);
}
return self;
}
- (void)dealloc {
if (_impl) {
av_frame_free(&_impl);
}
}
- (int32_t)width {
return _impl->width;
}
- (int32_t)height {
return _impl->height;
}
- (uint8_t **)data {
return _impl->data;
}
- (int *)lineSize {
return _impl->linesize;
}
- (int64_t)pts {
return _impl->pts;
}
- (FFMpegAVFrameNativePixelFormat)nativePixelFormat {
switch (_impl->format) {
case AV_PIX_FMT_VIDEOTOOLBOX: {
return FFMpegAVFrameNativePixelFormatVideoToolbox;
}
default: {
return FFMpegAVFrameNativePixelFormatUnknown;
}
}
}
- (int64_t)duration {
#if LIBAVFORMAT_VERSION_MAJOR >= 59
return _impl->duration;
#else
return _impl->pkt_duration;
#endif
}
- (FFMpegAVFrameColorRange)colorRange {
switch (_impl->color_range) {
case AVCOL_RANGE_MPEG:
case AVCOL_RANGE_UNSPECIFIED:
return FFMpegAVFrameColorRangeRestricted;
default:
return FFMpegAVFrameColorRangeFull;
}
}
- (void *)impl {
return _impl;
}
- (FFMpegAVFramePixelFormat)pixelFormat {
switch (_impl->format) {
case AV_PIX_FMT_YUVA420P:
return FFMpegAVFramePixelFormatYUVA;
default:
return FFMpegAVFramePixelFormatYUV;
}
}
@end
@@ -0,0 +1,46 @@
#import <FFMpegBinding/FFMpegAVIOContext.h>
#import "libavformat/avformat.h"
int FFMPEG_CONSTANT_AVERROR_EOF = AVERROR_EOF;
@interface FFMpegAVIOContext () {
AVIOContext *_impl;
}
@end
@implementation FFMpegAVIOContext
- (instancetype _Nullable)initWithBufferSize:(int32_t)bufferSize opaqueContext:(void * const _Nullable)opaqueContext readPacket:(int (* _Nullable)(void * _Nullable opaque, uint8_t * _Nullable buf, int buf_size))readPacket writePacket:(int (* _Nullable)(void * _Nullable opaque, uint8_t const * _Nullable buf, int buf_size))writePacket seek:(int64_t (*)(void * _Nullable opaque, int64_t offset, int whence))seek isSeekable:(bool)isSeekable {
self = [super init];
if (self != nil) {
void *avIoBuffer = av_malloc(bufferSize);
_impl = avio_alloc_context(avIoBuffer, bufferSize, 0, opaqueContext, readPacket, writePacket, seek);
if (_impl == nil) {
av_free(avIoBuffer);
return nil;
}
_impl->direct = 0;
if (!isSeekable) {
_impl->seekable = 0;
}
}
return self;
}
- (void)dealloc {
if (_impl != nil) {
if (_impl->buffer != nil) {
av_free(_impl->buffer);
}
av_free(_impl);
}
}
- (void *)impl {
return _impl;
}
@end
@@ -0,0 +1,15 @@
#import <FFMpegBinding/FFMpegGlobals.h>
#import "libavformat/avformat.h"
@implementation FFMpegGlobals
+ (void)initializeGlobals {
#if DEBUG
av_log_set_level(AV_LOG_ERROR);
#else
av_log_set_level(AV_LOG_QUIET);
#endif
}
@end
@@ -0,0 +1,185 @@
#import <FFMpegBinding/FFMpegLiveMuxer.h>
#import <FFMpegBinding/FFMpegAVIOContext.h>
#include "libavutil/timestamp.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswresample/swresample.h"
#define MOV_TIMESCALE 1000
@implementation FFMpegLiveMuxer
+ (bool)remux:(NSString * _Nonnull)path to:(NSString * _Nonnull)outPath offsetSeconds:(double)offsetSeconds {
AVFormatContext *input_format_context = NULL, *output_format_context = NULL;
AVPacket packet;
const char *in_filename, *out_filename;
int ret, i;
int stream_index = 0;
int *streams_list = NULL;
int number_of_streams = 0;
in_filename = [path UTF8String];
out_filename = [outPath UTF8String];
if ((ret = avformat_open_input(&input_format_context, in_filename, av_find_input_format("mp4"), NULL)) < 0) {
fprintf(stderr, "Could not open input file '%s'\n", in_filename);
goto end;
}
if ((ret = avformat_find_stream_info(input_format_context, NULL)) < 0) {
fprintf(stderr, "Failed to retrieve input stream information\n");
goto end;
}
avformat_alloc_output_context2(&output_format_context, NULL, "mpegts", out_filename);
if (!output_format_context) {
fprintf(stderr, "Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}
number_of_streams = input_format_context->nb_streams;
streams_list = av_malloc_array(number_of_streams, sizeof(*streams_list));
if (!streams_list) {
ret = AVERROR(ENOMEM);
goto end;
}
for (i = 0; i < input_format_context->nb_streams; i++) {
AVStream *out_stream;
AVStream *in_stream = input_format_context->streams[i];
AVCodecParameters *in_codecpar = in_stream->codecpar;
if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO && in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO) {
streams_list[i] = -1;
continue;
}
streams_list[i] = stream_index++;
if (in_codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
out_stream = avformat_new_stream(output_format_context, NULL);
if (!out_stream) {
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
if (ret < 0) {
fprintf(stderr, "Failed to copy codec parameters\n");
goto end;
}
out_stream->time_base = in_stream->time_base;
out_stream->duration = in_stream->duration;
} else if (in_codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
if (in_codecpar->codec_id != AV_CODEC_ID_AAC) {
streams_list[i] = -1;
continue;
}
out_stream = avformat_new_stream(output_format_context, NULL);
if (!out_stream) {
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
if (ret < 0) {
fprintf(stderr, "Failed to copy codec parameters\n");
goto end;
}
out_stream->time_base = in_stream->time_base;
out_stream->duration = in_stream->duration;
}
}
if (!(output_format_context->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&output_format_context->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Could not open output file '%s'\n", out_filename);
goto end;
}
}
AVDictionary* opts = NULL;
ret = avformat_write_header(output_format_context, &opts);
if (ret < 0) {
fprintf(stderr, "Error occurred when opening output file\n");
goto end;
}
while (1) {
AVStream *in_stream, *out_stream;
ret = av_read_frame(input_format_context, &packet);
if (ret < 0)
break;
in_stream = input_format_context->streams[packet.stream_index];
if (packet.stream_index >= number_of_streams || streams_list[packet.stream_index] < 0) {
av_packet_unref(&packet);
continue;
}
packet.stream_index = streams_list[packet.stream_index];
out_stream = output_format_context->streams[packet.stream_index];
if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
packet.pts = av_rescale_q_rnd(packet.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
packet.dts = av_rescale_q_rnd(packet.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
packet.pts += (int64_t)(offsetSeconds * out_stream->time_base.den);
packet.dts += (int64_t)(offsetSeconds * out_stream->time_base.den);
packet.duration = av_rescale_q(packet.duration, in_stream->time_base, out_stream->time_base);
packet.pos = -1;
ret = av_interleaved_write_frame(output_format_context, &packet);
if (ret < 0) {
fprintf(stderr, "Error muxing packet\n");
av_packet_unref(&packet);
break;
}
} else {
packet.pts = av_rescale_q_rnd(packet.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
packet.dts = av_rescale_q_rnd(packet.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
packet.pts += (int64_t)(offsetSeconds * out_stream->time_base.den);
packet.dts += (int64_t)(offsetSeconds * out_stream->time_base.den);
packet.duration = av_rescale_q(packet.duration, in_stream->time_base, out_stream->time_base);
packet.pos = -1;
ret = av_interleaved_write_frame(output_format_context, &packet);
if (ret < 0) {
fprintf(stderr, "Error muxing packet\n");
av_packet_unref(&packet);
break;
}
}
av_packet_unref(&packet);
}
av_write_trailer(output_format_context);
end:
if (input_format_context) {
avformat_close_input(&input_format_context);
}
if (output_format_context && !(output_format_context->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&output_format_context->pb);
}
if (output_format_context) {
avformat_free_context(output_format_context);
}
if (streams_list) {
av_freep(&streams_list);
}
if (ret < 0 && ret != AVERROR_EOF) {
fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
return false;
}
//printf("Remuxed video into %s\n", outPath.UTF8String);
return true;
}
@end
@@ -0,0 +1,108 @@
#import <FFMpegBinding/FFMpegOpusTrimmer.h>
#import "libavformat/avformat.h"
#import "libavutil/avutil.h"
@implementation FFMpegOpusTrimmer
+ (bool)trim:(NSString * _Nonnull)inputPath
to:(NSString * _Nonnull)outputPath
start:(double)start
end:(double)end
{
AVFormatContext *inCtx = NULL;
int ret;
if ((ret = avformat_open_input(&inCtx, inputPath.UTF8String, NULL, NULL)) < 0) {
return false;
}
if ((ret = avformat_find_stream_info(inCtx, NULL)) < 0) {
return false;
}
int audioIdx = -1;
for (unsigned i = 0; i < inCtx->nb_streams; ++i) {
if (inCtx->streams[i]->codecpar->codec_id == AV_CODEC_ID_OPUS) {
audioIdx = (int)i; break;
}
}
if (audioIdx == -1) {
avformat_close_input(&inCtx);
return false;
}
AVStream *inSt = inCtx->streams[audioIdx];
AVRational tb = inSt->time_base;
AVFormatContext *outCtx = NULL;
avformat_alloc_output_context2(&outCtx, NULL, "ogg",
outputPath.UTF8String);
if (!outCtx) {
avformat_close_input(&inCtx);
return false;
}
AVStream *outSt = avformat_new_stream(outCtx, NULL);
avcodec_parameters_copy(outSt->codecpar, inSt->codecpar);
outSt->time_base = tb;
if (!(outCtx->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&outCtx->pb, outputPath.UTF8String, AVIO_FLAG_WRITE) < 0) {
avformat_free_context(outCtx);
avformat_close_input(&inCtx);
return false;
}
}
ret = avformat_write_header(outCtx, NULL);
int64_t startTs = (int64_t)(start / av_q2d(tb));
int64_t endTs = (int64_t)(end / av_q2d(tb));
//int64_t span = MAX(endTs - startTs, 1);
av_seek_frame(inCtx, audioIdx, startTs, AVSEEK_FLAG_BACKWARD);
AVPacket *pkt = nil;
pkt = av_packet_alloc();
//double lastPct = 0.0;
int64_t firstPts = startTs;
while (av_read_frame(inCtx, pkt) >= 0) {
if (pkt->stream_index != audioIdx) { av_packet_unref(pkt); continue; }
if (pkt->pts < startTs) { av_packet_unref(pkt); continue; }
if (pkt->pts > endTs) { av_packet_unref(pkt); break; }
//double pct = (double)(pkt.pts - startTs) / (double)span;
//if (pct - lastPct >= 0.01 && progress) {
// lastPct = pct;
//dispatch_async(dispatch_get_main_queue(), ^{ progress(pct); });
//}
pkt->pts = av_rescale_q(pkt->pts - firstPts, tb, outSt->time_base);
pkt->dts = av_rescale_q(pkt->dts - firstPts, tb, outSt->time_base);
pkt->duration = av_rescale_q(pkt->duration, tb, outSt->time_base);
pkt->pos = -1;
pkt->stream_index = 0;
if (av_interleaved_write_frame(outCtx, pkt) != 0) {
av_packet_unref(pkt);
av_write_trailer(outCtx);
avio_closep(&outCtx->pb);
avformat_free_context(outCtx);
avformat_close_input(&inCtx);
return false;
}
av_packet_unref(pkt);
}
av_write_trailer(outCtx);
avio_closep(&outCtx->pb);
avformat_free_context(outCtx);
avformat_close_input(&inCtx);
return true;
// if (progress) {
// dispatch_async(dispatch_get_main_queue(), ^{ progress(1.0); });
// }
}
@end
@@ -0,0 +1,72 @@
#import <FFMpegBinding/FFMpegPacket.h>
#import <FFMpegBinding/FFMpegAVCodecContext.h>
#import "libavcodec/avcodec.h"
#import "libavformat/avformat.h"
@interface FFMpegPacket () {
AVPacket *_impl;
}
@end
@implementation FFMpegPacket
- (instancetype)init {
self = [super init];
if (self != nil) {
_impl = av_packet_alloc();
}
return self;
}
- (void)dealloc {
av_packet_free(&_impl);
}
- (void *)impl {
return _impl;
}
- (int64_t)pts {
if (_impl->pts == 0x8000000000000000) {
return _impl->dts;
} else {
return _impl->pts;
}
}
- (int64_t)dts {
return _impl->dts;
}
- (int64_t)duration {
return _impl->duration;
}
- (int32_t)streamIndex {
return (int32_t)_impl->stream_index;
}
- (int32_t)size {
return (int32_t)_impl->size;
}
- (bool)isKeyframe {
return (_impl->flags & AV_PKT_FLAG_KEY) != 0;
}
- (uint8_t *)data {
return _impl->data;
}
- (int32_t)sendToDecoder:(FFMpegAVCodecContext *)codecContext {
return avcodec_send_packet((AVCodecContext *)[codecContext impl], _impl);
}
- (void)reuse {
av_packet_unref(_impl);
}
@end
@@ -0,0 +1,223 @@
#import <FFMpegBinding/FFMpegRemuxer.h>
#import <FFMpegBinding/FFMpegAVIOContext.h>
#include "libavutil/timestamp.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#define MOV_TIMESCALE 1000
@interface FFMpegRemuxerContext : NSObject {
@public
int _fd;
int64_t _offset;
}
@end
@implementation FFMpegRemuxerContext
- (instancetype)initWithFileName:(NSString *)fileName {
self = [super init];
if (self != nil) {
_fd = open(fileName.UTF8String, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
}
return self;
}
- (void)dealloc {
if (_fd > 0) {
close(_fd);
}
}
@end
/*static int readPacketImpl(void * _Nullable opaque, uint8_t * _Nullable buffer, int length) {
FFMpegRemuxerContext *context = (__bridge FFMpegRemuxerContext *)opaque;
context->_offset += length;
printf("read %lld bytes (offset is now %lld)\n", (int64_t)length, context->_offset);
return (int)read(context->_fd, buffer, length);
}
static int writePacketImpl(void * _Nullable opaque, uint8_t * _Nullable buffer, int length) {
FFMpegRemuxerContext *context = (__bridge FFMpegRemuxerContext *)opaque;
context->_offset += length;
printf("write %lld bytes (offset is now %lld)\n", (int64_t)length, context->_offset);
return (int)write(context->_fd, buffer, length);
}
static int64_t seekImpl(void * _Nullable opaque, int64_t offset, int whence) {
FFMpegRemuxerContext *context = (__bridge FFMpegRemuxerContext *)opaque;
printf("seek to %lld\n", offset);
if (whence == FFMPEG_AVSEEK_SIZE) {
return 0;
} else {
context->_offset = offset;
return lseek(context->_fd, offset, SEEK_SET);
}
}*/
@implementation FFMpegRemuxer
+ (bool)remux:(NSString * _Nonnull)path to:(NSString * _Nonnull)outPath {
AVFormatContext *input_format_context = NULL, *output_format_context = NULL;
AVPacket packet;
const char *in_filename, *out_filename;
int ret, i;
int stream_index = 0;
int *streams_list = NULL;
int number_of_streams = 0;
int fragmented_mp4_options = 1;
in_filename = [path UTF8String];
out_filename = [outPath UTF8String];
//FFMpegRemuxerContext *outputContext = [[FFMpegRemuxerContext alloc] initWithFileName:outPath];
//FFMpegAVIOContext *outputIoContext = [[FFMpegAVIOContext alloc] initWithBufferSize:1024 opaqueContext:(__bridge void *)outputContext readPacket:&readPacketImpl writePacket:&writePacketImpl seek:&seekImpl];
if ((ret = avformat_open_input(&input_format_context, in_filename, av_find_input_format("mov"), NULL)) < 0) {
fprintf(stderr, "Could not open input file '%s'", in_filename);
goto end;
}
if ((ret = avformat_find_stream_info(input_format_context, NULL)) < 0) {
fprintf(stderr, "Failed to retrieve input stream information");
goto end;
}
avformat_alloc_output_context2(&output_format_context, NULL, NULL, out_filename);
//output_format_context = avformat_alloc_context();
//output_format_context->pb = outputIoContext.impl;
//output_format_context->flags |= AVFMT_FLAG_CUSTOM_IO;
//output_format_context->oformat = av_guess_format("mp4", NULL, NULL);
if (!output_format_context) {
fprintf(stderr, "Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}
number_of_streams = input_format_context->nb_streams;
streams_list = av_malloc_array(number_of_streams, sizeof(*streams_list));
if (!streams_list) {
ret = AVERROR(ENOMEM);
goto end;
}
int64_t maxTrackLength = 0;
for (i = 0; i < input_format_context->nb_streams; i++) {
AVStream *out_stream;
AVStream *in_stream = input_format_context->streams[i];
AVCodecParameters *in_codecpar = in_stream->codecpar;
if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
streams_list[i] = -1;
continue;
}
if (in_stream->time_base.den != 0) {
int64_t trackLength = av_rescale_rnd(in_stream->duration, MOV_TIMESCALE, (int64_t)in_stream->time_base.den, AV_ROUND_UP);
maxTrackLength = MAX(trackLength, maxTrackLength);
/*int64_t max_track_len_temp = av_rescale_rnd(mov->tracks[i].track_duration,
MOV_TIMESCALE,
mov->tracks[i].timescale,
AV_ROUND_UP);*/
}
streams_list[i] = stream_index++;
out_stream = avformat_new_stream(output_format_context, NULL);
out_stream->time_base = in_stream->time_base;
out_stream->duration = in_stream->duration;
if (!out_stream) {
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
if (ret < 0) {
fprintf(stderr, "Failed to copy codec parameters\n");
goto end;
}
}
// https://ffmpeg.org/doxygen/trunk/group__lavf__misc.html#gae2645941f2dc779c307eb6314fd39f10
//av_dump_format(output_format_context, 0, out_filename, 1);
// unless it's a no file (we'll talk later about that) write to the disk (FLAG_WRITE)
// but basically it's a way to save the file to a buffer so you can store it
// wherever you want.
if (!(output_format_context->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&output_format_context->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Could not open output file '%s'", out_filename);
goto end;
}
}
AVDictionary* opts = NULL;
if (fragmented_mp4_options) {
// https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_Extensions_API/Transcoding_assets_for_MSE
av_dict_set(&opts, "movflags", "dash+faststart+global_sidx+skip_trailer", 0);
if (maxTrackLength > 0) {
//av_dict_set_int(&opts, "custom_maxTrackLength", maxTrackLength, 0);
}
}
// https://ffmpeg.org/doxygen/trunk/group__lavf__encoding.html#ga18b7b10bb5b94c4842de18166bc677cb
ret = avformat_write_header(output_format_context, &opts);
if (ret < 0) {
fprintf(stderr, "Error occurred when opening output file\n");
goto end;
}
while (1) {
AVStream *in_stream, *out_stream;
ret = av_read_frame(input_format_context, &packet);
if (ret < 0)
break;
in_stream = input_format_context->streams[packet.stream_index];
if (packet.stream_index >= number_of_streams || streams_list[packet.stream_index] < 0) {
av_packet_unref(&packet);
continue;
}
packet.stream_index = streams_list[packet.stream_index];
out_stream = output_format_context->streams[packet.stream_index];
/* copy packet */
packet.pts = av_rescale_q_rnd(packet.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
packet.dts = av_rescale_q_rnd(packet.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
packet.duration = av_rescale_q(packet.duration, in_stream->time_base, out_stream->time_base);
// https://ffmpeg.org/doxygen/trunk/structAVPacket.html#ab5793d8195cf4789dfb3913b7a693903
packet.pos = -1;
//https://ffmpeg.org/doxygen/trunk/group__lavf__encoding.html#ga37352ed2c63493c38219d935e71db6c1
ret = av_interleaved_write_frame(output_format_context, &packet);
if (ret < 0) {
fprintf(stderr, "Error muxing packet\n");
break;
}
av_packet_unref(&packet);
}
//https://ffmpeg.org/doxygen/trunk/group__lavf__encoding.html#ga7f14007e7dc8f481f054b21614dfec13
av_write_trailer(output_format_context);
end:
avformat_close_input(&input_format_context);
/* close output */
if (output_format_context && !(output_format_context->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&output_format_context->pb);
}
avformat_free_context(output_format_context);
av_freep(&streams_list);
if (ret < 0 && ret != AVERROR_EOF) {
fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
return false;
}
printf("Remuxed video into %s\n", outPath.UTF8String);
return true;
}
@end
@@ -0,0 +1,139 @@
#import <FFMpegBinding/FFMpegSWResample.h>
#import <FFMpegBinding/FFMpegAVFrame.h>
#import "libavformat/avformat.h"
#import "libavcodec/avcodec.h"
#import "libswresample/swresample.h"
@interface FFMpegSWResample () {
int _sourceSampleRate;
FFMpegAVSampleFormat _sourceSampleFormat;
int _destinationChannelCount;
int _destinationSampleRate;
FFMpegAVSampleFormat _destinationSampleFormat;
int _currentSourceChannelCount;
SwrContext *_context;
NSUInteger _ratio;
void *_buffer;
int _bufferSize;
}
@end
@implementation FFMpegSWResample
- (instancetype)initWithSourceChannelCount:(NSInteger)sourceChannelCount sourceSampleRate:(NSInteger)sourceSampleRate sourceSampleFormat:(enum FFMpegAVSampleFormat)sourceSampleFormat destinationChannelCount:(NSInteger)destinationChannelCount destinationSampleRate:(NSInteger)destinationSampleRate destinationSampleFormat:(enum FFMpegAVSampleFormat)destinationSampleFormat {
self = [super init];
if (self != nil) {
_sourceSampleRate = (int)sourceSampleRate;
_sourceSampleFormat = sourceSampleFormat;
_destinationChannelCount = (int)destinationChannelCount;
_destinationSampleRate = (int)destinationSampleRate;
_destinationSampleFormat = destinationSampleFormat;
_currentSourceChannelCount = -1;
}
return self;
}
- (void)dealloc {
if (_context) {
swr_free(&_context);
}
if (_buffer) {
free(_buffer);
}
}
- (void)resetContextForChannelCount:(int)channelCount {
if (_context) {
swr_free(&_context);
_context = NULL;
}
#if LIBAVFORMAT_VERSION_MAJOR >= 59
AVChannelLayout channelLayoutIn;
av_channel_layout_default(&channelLayoutIn, channelCount);
AVChannelLayout channelLayoutOut;
av_channel_layout_default(&channelLayoutOut, _destinationChannelCount);
if (swr_alloc_set_opts2(
&_context,
&channelLayoutOut,
(enum AVSampleFormat)_destinationSampleFormat,
(int)_destinationSampleRate,
&channelLayoutIn,
(enum AVSampleFormat)_sourceSampleFormat,
(int)_sourceSampleRate,
0,
NULL
) != 0) {
return;
}
#else
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_context = swr_alloc_set_opts(NULL,
av_get_default_channel_layout((int)_destinationChannelCount),
(enum AVSampleFormat)_destinationSampleFormat,
(int)_destinationSampleRate,
av_get_default_channel_layout(channelCount),
(enum AVSampleFormat)_sourceSampleFormat,
(int)_sourceSampleRate,
0,
NULL);
#pragma clang diagnostic pop
#endif
_currentSourceChannelCount = channelCount;
_ratio = MAX(1, _destinationSampleRate / MAX(_sourceSampleRate, 1)) * MAX(1, _destinationChannelCount / channelCount) * 2;
if (_context) {
swr_init(_context);
}
}
- (NSData * _Nullable)resample:(FFMpegAVFrame *)frame {
AVFrame *frameImpl = (AVFrame *)[frame impl];
#if LIBAVFORMAT_VERSION_MAJOR >= 59
int numChannels = frameImpl->ch_layout.nb_channels;
#else
int numChannels = frameImpl->channels;
#endif
if (numChannels != _currentSourceChannelCount) {
[self resetContextForChannelCount:numChannels];
}
if (!_context) {
return nil;
}
int bufSize = av_samples_get_buffer_size(NULL,
(int)_destinationChannelCount,
frameImpl->nb_samples * (int)_ratio,
(enum AVSampleFormat)_destinationSampleFormat,
1);
if (!_buffer || _bufferSize < bufSize) {
_bufferSize = bufSize;
_buffer = realloc(_buffer, _bufferSize);
}
Byte *outbuf[2] = { _buffer, 0 };
int numFrames = swr_convert(_context,
outbuf,
frameImpl->nb_samples * (int)_ratio,
(const uint8_t **)frameImpl->data,
frameImpl->nb_samples);
if (numFrames <= 0) {
return nil;
}
return [[NSData alloc] initWithBytes:_buffer length:numFrames * _destinationChannelCount * 2];
}
@end
+227
View File
@@ -0,0 +1,227 @@
#import <FFMpegBinding/FFMpegVideoWriter.h>
#import <FFMpegBinding/FFMpegAVFrame.h>
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libavutil/imgutils.h"
@interface FFMpegVideoWriter ()
@property (nonatomic) AVFormatContext *formatContext;
@property (nonatomic) AVCodecContext *codecContext;
@property (nonatomic) AVStream *stream;
@property (nonatomic) int64_t framePts;
@end
@implementation FFMpegVideoWriter
- (instancetype)init {
self = [super init];
if (self) {
_framePts = -1;
}
return self;
}
- (bool)setupWithOutputPath:(NSString *)outputPath width:(int)width height:(int)height bitrate:(int64_t)bitrate framerate:(int32_t)framerate {
avformat_alloc_output_context2(&_formatContext, nil, "matroska", [outputPath UTF8String]);
if (!_formatContext) {
return false;
}
if (avio_open(&_formatContext->pb, [outputPath UTF8String], AVIO_FLAG_WRITE) < 0) {
avformat_free_context(_formatContext);
_formatContext = nil;
return false;
}
const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_VP9);
if (!codec) {
avio_closep(&_formatContext->pb);
avformat_free_context(_formatContext);
_formatContext = nil;
return false;
}
_codecContext = avcodec_alloc_context3(codec);
if (!_codecContext) {
avio_closep(&_formatContext->pb);
avformat_free_context(_formatContext);
_formatContext = nil;
return false;
}
_codecContext->codec_id = AV_CODEC_ID_VP9;
_codecContext->codec_type = AVMEDIA_TYPE_VIDEO;
_codecContext->pix_fmt = AV_PIX_FMT_YUVA420P;
_codecContext->color_range = AVCOL_RANGE_MPEG;
_codecContext->color_primaries = AVCOL_PRI_BT709;
_codecContext->colorspace = AVCOL_SPC_BT709;
_codecContext->width = width;
_codecContext->height = height;
_codecContext->time_base = (AVRational){1, framerate};
_codecContext->framerate = (AVRational){framerate, 1};
_codecContext->bit_rate = bitrate;
if (_formatContext->oformat->flags & AVFMT_GLOBALHEADER) {
_codecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
if (avcodec_open2(_codecContext, codec, NULL) < 0) {
avcodec_free_context(&_codecContext);
_codecContext = nil;
avio_closep(&_formatContext->pb);
avformat_free_context(_formatContext);
_formatContext = nil;
return false;
}
_stream = avformat_new_stream(_formatContext, codec);
if (!_stream) {
#if LIBAVFORMAT_VERSION_MAJOR >= 59
#else
avcodec_close(_codecContext);
#endif
avcodec_free_context(&_codecContext);
_codecContext = nil;
avio_closep(&_formatContext->pb);
avformat_free_context(_formatContext);
_formatContext = nil;
return false;
}
_stream->codecpar->codec_id = _codecContext->codec_id;
_stream->codecpar->codec_type = _codecContext->codec_type;
_stream->codecpar->width = _codecContext->width;
_stream->codecpar->height = _codecContext->height;
_stream->codecpar->format = _codecContext->pix_fmt;
_stream->time_base = _codecContext->time_base;
int ret = avcodec_parameters_from_context(_stream->codecpar, _codecContext);
if (ret < 0) {
#if LIBAVFORMAT_VERSION_MAJOR >= 59
#else
avcodec_close(_codecContext);
#endif
avcodec_free_context(&_codecContext);
_codecContext = nil;
avio_closep(&_formatContext->pb);
avformat_free_context(_formatContext);
_formatContext = nil;
return false;
}
ret = avformat_write_header(_formatContext, nil);
if (ret < 0) {
#if LIBAVFORMAT_VERSION_MAJOR >= 59
#else
avcodec_close(_codecContext);
#endif
avcodec_free_context(&_codecContext);
_codecContext = nil;
avio_closep(&_formatContext->pb);
avformat_free_context(_formatContext);
_formatContext = nil;
return false;
}
return true;
}
- (bool)encodeFrame:(FFMpegAVFrame *)frame {
if (!_codecContext || !_stream) {
return false;
}
self.framePts++;
AVFrame *frameImpl = (AVFrame *)[frame impl];
frameImpl->pts = self.framePts;
frameImpl->color_range = AVCOL_RANGE_MPEG;
frameImpl->color_primaries = AVCOL_PRI_BT709;
frameImpl->colorspace = AVCOL_SPC_BT709;
int sendRet = avcodec_send_frame(_codecContext, frameImpl);
if (sendRet < 0) {
return false;
}
AVPacket *pkt = nil;
pkt = av_packet_alloc();
pkt->data = nil;
pkt->size = 0;
while (sendRet >= 0) {
int recvRet = avcodec_receive_packet(_codecContext, pkt);
if (recvRet == AVERROR(EAGAIN) || recvRet == AVERROR_EOF) {
break;
} else if (recvRet < 0) {
av_packet_unref(pkt);
break;
}
av_packet_rescale_ts(pkt, _codecContext->time_base, _stream->time_base);
pkt->stream_index = _stream->index;
int ret = av_interleaved_write_frame(_formatContext, pkt);
av_packet_unref(pkt);
if (ret < 0) {
return false;
}
}
av_packet_free(&pkt);
return true;
}
- (bool)finalizeVideo {
if (!_codecContext) {
return false;
}
int sendRet = avcodec_send_frame(_codecContext, NULL);
if (sendRet >= 0) {
AVPacket *pkt = nil;
pkt = av_packet_alloc();
pkt->data = nil;
pkt->size = 0;
while (avcodec_receive_packet(_codecContext, pkt) == 0) {
av_packet_rescale_ts(pkt, _codecContext->time_base, _stream->time_base);
pkt->stream_index = _stream->index;
av_interleaved_write_frame(_formatContext, pkt);
av_packet_unref(pkt);
}
av_packet_free(&pkt);
}
if (_formatContext) {
av_write_trailer(_formatContext);
}
if (_formatContext && _formatContext->pb) {
avio_closep(&_formatContext->pb);
}
if (_codecContext) {
#if LIBAVFORMAT_VERSION_MAJOR >= 59
#else
avcodec_close(_codecContext);
#endif
avcodec_free_context(&_codecContext);
_codecContext = nil;
}
if (_formatContext) {
avformat_free_context(_formatContext);
_formatContext = nil;
}
return true;
}
@end
+8
View File
@@ -0,0 +1,8 @@
#import <FFMpegBinding/FrameConverter.h>
void fillDstPlane(uint8_t * _Nonnull dstPlane, uint8_t * _Nonnull srcPlane1, uint8_t * _Nonnull srcPlane2, size_t srcPlaneSize) {
for (size_t i = 0; i < srcPlaneSize; i++){
dstPlane[2 * i] = srcPlane1[i];
dstPlane[2 * i + 1] = srcPlane2[i];
}
}