Объединение нескольких видео в iOS с помощью AVMutableVideoComposition

image Здравствуй, Хабр!
В одном из проектов мне понадобилось решить задачу объединения видео, в частности, пользователь мог поставить видео на паузу, после чего продолжить запись (количество итераций было неизвестно). Поэтому необходимо было найти способ для решения этой задачи доступными средствами. Конечно, в голову пришло два варианта, либо писать всё сразу в один файл, либо записывать в разные, а склеивать уже после сессии. Я решил остановиться на втором, а что из этого вышло, читайте под катом.

Для записи видео я использовал AVCamCaptureManager, основываясь на приложении AVCam, любезно расположенном на всеми нами любимом сайте. Ну а после нажатия кнопки стоп начиналось самое интересное.

Этап 1. Подготовка.
На этом этапе нужно сделать следующее:
  • Объект AVMutableComposition. В нём будут наши дорожки.
    AVMutableComposition
    AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];

  • Массив инструкций для трэков и объект AVMutableVideoCompositionInstruction для них.
    AVMutableVideoCompositionInstruction
    NSMutableArray *arrayInstruction = [[NSMutableArray alloc] init];
    AVMutableVideoCompositionInstruction *MainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];

  • Общая аудиодорожка. Переменная isSound указывает, нужна ли она здесь (согласно поставленной задаче пользователь мог записывать видео как со звуком, так и без него).
    Общая аудиодорожка
       AVMutableCompositionTrack *audioTrack;
       if(self.isSoundOn==YES)
    		audioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio
    											preferredTrackID:kCMPersistentTrackID_Invalid];

  • Переменная для хранения длительности (а также она нужна для определения границ склейки).
    	CMTime duration = kCMTimeZero;


Этап 2. Поиск файлов, создание ассетов, генерация инструкций и трансформация видеодорожки при необходимости.
  • Пробегаемся по всем файлам и создаём ассеты для каждого из них. Всё это можно и нужно делать циклом, переменная i-здесь индекс файла.
    Создание ассетов
    Все видео я записывал в темповую директорию, при этом увеличивая значение i. Соответственно, ассеты создаются из этих файлов.
    AVAsset *currentAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:[NSString stringWithFormat:@"%@%@%d%@", NSTemporaryDirectory(), @"Movie",i,@".mov"]]];

  • В цикле создаются AVMutableCompositionTrack, в каждый из которых вставляется временной отрезок CMTimeRange из созданного ассета.
    Создание треков
    //VIDEO TRACK
    AVMutableCompositionTrack *currentTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    [currentTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, currentAsset.duration) ofTrack:[[currentAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:duration error:nil];
    Особое внимание здесь стоит уделить тому, куда именно нужно вставлять отрезок, если вы хотите избежать чёрных полос во время проигрывания, или что ещё хуже, накладывания одного видео на другое в процессе воспроизведения. И даже не спрашивайте, почему я акцентирую на этом внимание.

  • AVMutableVideoCompositionLayerInstruction используется для создания инструкции для конкретного слоя дорожки. Именно в него записывается возможная трансформация.
    AVMutableVideoCompositionLayerInstruction
    AVMutableVideoCompositionLayerInstruction *currentAssetLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:currentTrack];
    При правильном определении положения видео (портретного или лэндскейпного) на выходе вы сможете получить шикарную картинку, в которой горизонтальное или вертикальное видео будет центрироваться в зависимости от того, дорожка в каком режиме у вас идёт первой.

  • Объект AVAssetTrack нужен для создания дорожки на основе уже существующего ассета с типом AVMediaTypeVideo.
    AVAssetTrack
    AVAssetTrack *currentAssetTrack = [[currentAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];

  • Увеличиваем значение duration.
    duration UP
    CMTime требует даже отдельного поста, конечно.
    duration=CMTimeAdd(duration, currentAsset.duration);


Этап 3. Установка параметров MainInstruction и формирование AVMutableVideoComposition
  • timeRange
    Общая длительность видеодорожки.
    MainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, duration);

  • layerInstructions
    Здесь нам понадобится массив инструкций, сформированный на втором этапе.
    MainInstruction.layerInstructions = arrayInstruction;

  • MainCompositionInst
    Создаём экземпляр MainCompositionInst с нужными параметрами
    		AVMutableVideoComposition *MainCompositionInst = [AVMutableVideoComposition videoComposition];
    		MainCompositionInst.instructions = [NSArray arrayWithObject:MainInstruction];
    		MainCompositionInst.frameDuration = CMTimeMake(1, 30);
    		MainCompositionInst.renderSize = CGSizeMake(320.0, 480.0);

  • Куда сохранять-то?
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    		NSString *documentsDirectory = [paths objectAtIndex:0];
    		NSString *myPathDocs =  [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"mergeVideo-%d.mov",arc4random() % 1000]];
    		
    		NSURL *url = [NSURL fileURLWithPath:myPathDocs];


Этап 4. Сохранение полученного видео через AVAssetExportSession
Разница между пресетами Highest и Medium очень даже существенна. Поэтому если вы планируете распространять видео в социальных сетях, настоятельно вам рекомендую использовать именно Medium.
  • Задаём качество и создаём сессию
    		NSString *quality = AVAssetExportPresetHighestQuality;
    		if(self.isHighQuality==NO)
    		quality = AVAssetExportPresetMediumQuality;
    		AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:quality]; 

  • Сохраняем!
    exporter.outputURL=url;
    		exporter.outputFileType = AVFileTypeQuickTimeMovie;
    		exporter.videoComposition = MainCompositionInst;
    		exporter.shouldOptimizeForNetworkUse = YES;
    		[exporter exportAsynchronouslyWithCompletionHandler:^
    		 { //здесь можно удалить темповые файлы.
    }];

Возможные статусы при экспорте:
Что тут у нас?
		 switch (exporter.status)
			 {
				 case AVAssetExportSessionStatusCompleted:
				 NSLog(@"Completed exporting!");
				 break;
				 case AVAssetExportSessionStatusFailed:
				 NSLog(@"Failed:%@", exporter.error.description);
				 break;
				 case AVAssetExportSessionStatusCancelled:
				 NSLog(@"Canceled:%@", exporter.error);
				 break;
				 case AVAssetExportSessionStatusExporting:
				 NSLog(@"Exporting!");
				 break;
				 case AVAssetExportSessionStatusWaiting:
				 NSLog(@"Waiting");
				 break;
				 default:
				 break;
			 }


Заключение

В статье я постарался акцентировать внимание именно на объединении видео, и думаю, многим хабровчанам, занимающимся разработкой под iOS, столкнувшимся с такой задачей, этот пост поможет в будущем. В дальнейшем я планирую более подробно рассказать о CMTime, и почему это не просто «время». Кстати, много полезной информации по теме есть на сайте. А сам метод вы можете найти в репозитории
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 0

Only users with full accounts can post comments. Log in, please.