Редактирование

Платформа Основы AV обеспечивает многофункциональный набор классов для упрощения редактирования аудио-визуальных активов. В основе редактирования Основы AV API является составами. Состав является просто набором дорожек от одной или более различных активов носителей. AVMutableComposition класс обеспечивает интерфейс для вставки и удаления дорожек, а также управления их временными упорядочиваниями. Вы используете непостоянный состав для соединения нового актива от комбинации существующих активов. Если все, что Вы хотите сделать, объединить многократные активы вместе последовательно в единственный файл, который является таким количеством подробности, как Вам нужно. Если Вы хотите выполнить какую-либо пользовательскую обработку аудиоданных или обработку видеоданных на дорожках в Вашем составе, необходимо включить аудио соединение или видео состав, соответственно.

../Art/avmutablecomposition_2x.png

Используя AVMutableAudioMix класс, можно выполнить пользовательскую обработку аудиоданных на аудиотреках в составе. В настоящее время можно указать максимальную громкость или установить скат объема для аудиотрека.

../Art/avmutableaudiomix_2x.png

Можно использовать AVMutableVideoComposition класс для работы непосредственно с видеотреками в составе в целях редактирования. С единственным видео составом можно указать желаемый размер рендеринга и масштаб, а также продолжительность кадра, для выходного видео. Через инструкции видео состава (представленный AVMutableVideoCompositionInstruction класс), можно изменить цвет фона видео и применить инструкции уровня. Эти инструкции уровня (представленный AVMutableVideoCompositionLayerInstruction класс), может использоваться для применения, преобразовывает, преобразуйте скаты, непрозрачность и скаты непрозрачности к видеотрекам в составе. Видео класс состава также предоставляет Вам возможность представить эффекты от Базовой платформы Анимации в Ваше видео с помощью animationTool свойство.

../Art/avmutablevideocomposition_2x.png../Art/avmutablevideocomposition_2x.png

Для объединения состава с аудио соединением и видео состава Вы используете AVAssetExportSession объект. Вы инициализируете сеанс экспорта со своим составом и затем просто присваиваете Ваше аудио соединение и видео состав к audioMix и videoComposition свойства соответственно.

../Art/puttingitalltogether_2x.png../Art/puttingitalltogether_2x.png

Создание состава

Для создания собственного состава Вы используете AVMutableComposition класс. Для добавления данных носителей к составу необходимо добавить одну или более дорожек состава, представленных AVMutableCompositionTrack класс. Самый простой случай создает непостоянный состав с одним видеотреком и одним аудиотреком:

AVMutableComposition *mutableComposition = [AVMutableComposition composition];
// Create the video composition track.
AVMutableCompositionTrack *mutableCompositionVideoTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
// Create the audio composition track.
AVMutableCompositionTrack *mutableCompositionAudioTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

Опции для инициализации дорожки состава

При добавлении новых дорожек к составу необходимо обеспечить и тип среды и дорожку ID. Несмотря на то, что аудио и видео являются обычно используемыми типами среды, можно указать другие типы среды также, такой как AVMediaTypeSubtitle или AVMediaTypeText.

Каждая дорожка, связанная с некоторыми аудиовизуальными данными, имеет уникальный идентификатор, называемый дорожкой ID. Если Вы указываете kCMPersistentTrackID_Invalid как предпочтительная дорожка ID, уникальный идентификатор автоматически сгенерирован для Вас и связан с дорожкой.

Добавление аудиовизуальных данных к составу

Как только у Вас есть состав с одной или более дорожками, можно начать добавлять данные носителей к надлежащим дорожкам. Для добавления данных носителей к дорожке состава Вам нужен доступ к AVAsset возразите, где расположены данные носителей. Можно использовать непостоянный интерфейс дорожки состава для размещения многократных дорожек с тем же базовым типом среды вместе на той же дорожке. Следующий пример иллюстрирует, как добавить две различных видео дорожки актива в последовательности к той же дорожке состава:

// You can retrieve AVAssets from a number of places, like the camera roll for example.
AVAsset *videoAsset = <#AVAsset with at least one video track#>;
AVAsset *anotherVideoAsset = <#another AVAsset with at least one video track#>;
// Get the first video track from each asset.
AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVAssetTrack *anotherVideoAssetTrack = [[anotherVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
// Add them both to the composition.
[mutableCompositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,videoAssetTrack.timeRange.duration) ofTrack:videoAssetTrack atTime:kCMTimeZero error:nil];
[mutableCompositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,anotherVideoAssetTrack.timeRange.duration) ofTrack:anotherVideoAssetTrack atTime:videoAssetTrack.timeRange.duration error:nil];

Получение совместимых дорожек состава

Где возможно, у Вас должна быть только одна дорожка состава для каждого типа среды. Это объединение совместимых дорожек актива приводит к минимальной сумме использования ресурсов. При представлении данных носителей последовательно, необходимо поместить любые данные носителей того же типа на той же дорожке состава. Можно запросить непостоянный состав, чтобы узнать, существуют ли какие-либо дорожки состава, совместимые с желаемой дорожкой актива:

AVMutableCompositionTrack *compatibleCompositionTrack = [mutableComposition mutableTrackCompatibleWithTrack:<#the AVAssetTrack you want to insert#>];
if (compatibleCompositionTrack) {
    // Implementation continues.
}

Генерация ската объема

Сингл AVMutableAudioMix объект может выполнить пользовательскую обработку аудиоданных на всех аудиотреках в Вашем составе индивидуально. Вы создаете аудио соединение с помощью audioMix метод класса, и Вы используете экземпляры AVMutableAudioMixInputParameters класс для соединения аудио соединения с определенными дорожками в составе. Аудио соединение может использоваться для варьирования объема аудиотрека. Следующий пример выводит на экран, как установить скат объема на определенном аудиотреке для медленного постепенного исчезновения аудио по продолжительности состава:

AVMutableAudioMix *mutableAudioMix = [AVMutableAudioMix audioMix];
// Create the audio mix input parameters object.
AVMutableAudioMixInputParameters *mixParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:mutableCompositionAudioTrack];
// Set the volume ramp to slowly fade the audio out over the duration of the composition.
[mixParameters setVolumeRampFromStartVolume:1.f toEndVolume:0.f timeRange:CMTimeRangeMake(kCMTimeZero, mutableComposition.duration)];
// Attach the input parameters to the audio mix.
mutableAudioMix.inputParameters = @[mixParameters];

Выполнение пользовательской обработки видеоданных

Как с аудио соединением, Вам только нужно то AVMutableVideoComposition объект выполнить всю Вашу пользовательскую обработку видеоданных на видеотреках Вашего состава. Используя видео состав, можно непосредственно установить надлежащий размер рендеринга, масштаб и частоту кадров для видеотреков состава. Для подробного примера установки надлежащих значений для этих свойств посмотрите Установку Размера Рендеринга и Продолжительность кадра.

Изменение цвета фона состава

Все видео составы должны также иметь массив AVVideoCompositionInstruction объекты, содержащие по крайней мере одну видео инструкцию состава. Вы используете AVMutableVideoCompositionInstruction класс для создания собственных видео инструкций состава. Используя видео инструкции состава, можно изменить цвет фона состава, указать, необходима ли обработка сообщения, или примените инструкции уровня.

Следующий пример иллюстрирует, как создать видео инструкцию состава, изменяющую цвет фона на красный для всего состава.

AVMutableVideoCompositionInstruction *mutableVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
mutableVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, mutableComposition.duration);
mutableVideoCompositionInstruction.backgroundColor = [[UIColor redColor] CGColor];

Применение скатов непрозрачности

Видео инструкции состава могут также использоваться для применения видео инструкций уровня состава. AVMutableVideoCompositionLayerInstruction объект может применяться, преобразовывает, преобразуйте скаты, непрозрачность и скаты непрозрачности к определенному видеотреку в составе. Порядок инструкций уровня в видео инструкции состава layerInstructions массив определяет, как видеокадры от исходных дорожек должны быть разделены на уровни и составлены на время той инструкции состава. Следующий фрагмент кода показывает, как установить скат непрозрачности для медленного постепенного исчезновения первого видео в составе прежде, чем перейти к второму видео:

AVAsset *firstVideoAssetTrack = <#AVAssetTrack representing the first video segment played in the composition#>;
AVAsset *secondVideoAssetTrack = <#AVAssetTrack representing the second video segment played in the composition#>;
// Create the first video composition instruction.
AVMutableVideoCompositionInstruction *firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set its time range to span the duration of the first video track.
firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration);
// Create the layer instruction and associate it with the composition video track.
AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableCompositionVideoTrack];
// Create the opacity ramp to fade out the first video track over its entire duration.
[firstVideoLayerInstruction setOpacityRampFromStartOpacity:1.f toEndOpacity:0.f timeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration)];
// Create the second video composition instruction so that the second video track isn't transparent.
AVMutableVideoCompositionInstruction *secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set its time range to span the duration of the second video track.
secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration));
// Create the second layer instruction and associate it with the composition video track.
AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableCompositionVideoTrack];
// Attach the first layer instruction to the first video composition instruction.
firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction];
// Attach the second layer instruction to the second video composition instruction.
secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction];
// Attach both of the video composition instructions to the video composition.
mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction];

Слияние базовых эффектов анимации

Видео состав может добавить питание Базовой Анимации к Вашему составу через animationTool свойство. Через этот инструмент анимации можно выполнить задачи, такие как создание водяных знаков на видео и добавление заголовков или анимация оверлейных программ. Базовая Анимация может использоваться двумя различными способами с видео составами: можно включить Базовый Слой анимации как его собственную отдельную дорожку состава, или можно представить Базовые Эффекты анимации (использующий Базовый Слой анимации) в видеокадры в составе непосредственно. Следующий код выводит на экран последнюю опцию путем добавления водяного знака к центру видео:

CALayer *watermarkLayer = <#CALayer representing your desired watermark image#>;
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];
parentLayer.frame = CGRectMake(0, 0, mutableVideoComposition.renderSize.width, mutableVideoComposition.renderSize.height);
videoLayer.frame = CGRectMake(0, 0, mutableVideoComposition.renderSize.width, mutableVideoComposition.renderSize.height);
[parentLayer addSublayer:videoLayer];
watermarkLayer.position = CGPointMake(mutableVideoComposition.renderSize.width/2, mutableVideoComposition.renderSize.height/4);
[parentLayer addSublayer:watermarkLayer];
mutableVideoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];

Соединение всего этого: объединение многократных активов и сохранение результата к рулону камеры

Этот краткий пример кода иллюстрирует, как можно объединить две видео дорожки актива и аудио дорожку актива для создания единственного видеофайла. Это показывает как:

Создание состава

Для соединения дорожек от отдельных активов Вы используете AVMutableComposition объект. Создайте состав и добавьте одно аудио и один видеотрек.

AVMutableComposition *mutableComposition = [AVMutableComposition composition];
AVMutableCompositionTrack *videoCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *audioCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

Добавление активов

Пустой состав делает Вас отрицательный результат. Добавьте две видео дорожки актива и аудио дорожку актива к составу.

AVAssetTrack *firstVideoAssetTrack = [[firstVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVAssetTrack *secondVideoAssetTrack = [[secondVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
[videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration) ofTrack:firstVideoAssetTrack atTime:kCMTimeZero error:nil];
[videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, secondVideoAssetTrack.timeRange.duration) ofTrack:secondVideoAssetTrack atTime:firstVideoAssetTrack.timeRange.duration error:nil];
[audioCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration)) ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:kCMTimeZero error:nil];

Проверка видео ориентаций

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

BOOL isFirstVideoPortrait = NO;
CGAffineTransform firstTransform = firstVideoAssetTrack.preferredTransform;
// Check the first video track's preferred transform to determine if it was recorded in portrait mode.
if (firstTransform.a == 0 && firstTransform.d == 0 && (firstTransform.b == 1.0 || firstTransform.b == -1.0) && (firstTransform.c == 1.0 || firstTransform.c == -1.0)) {
    isFirstVideoPortrait = YES;
}
BOOL isSecondVideoPortrait = NO;
CGAffineTransform secondTransform = secondVideoAssetTrack.preferredTransform;
// Check the second video track's preferred transform to determine if it was recorded in portrait mode.
if (secondTransform.a == 0 && secondTransform.d == 0 && (secondTransform.b == 1.0 || secondTransform.b == -1.0) && (secondTransform.c == 1.0 || secondTransform.c == -1.0)) {
    isSecondVideoPortrait = YES;
}
if ((isFirstVideoAssetPortrait && !isSecondVideoAssetPortrait) || (!isFirstVideoAssetPortrait && isSecondVideoAssetPortrait)) {
    UIAlertView *incompatibleVideoOrientationAlert = [[UIAlertView alloc] initWithTitle:@"Error!" message:@"Cannot combine a video shot in portrait mode with a video shot in landscape mode." delegate:self cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];
    [incompatibleVideoOrientationAlert show];
    return;
}

Применение видео инструкций уровня состава

Как только Вы знаете, что видео сегменты имеют совместимые ориентации, можно применить необходимые инструкции уровня к каждому и добавить эти инструкции уровня к видео составу.

AVMutableVideoCompositionInstruction *firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set the time range of the first instruction to span the duration of the first video track.
firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration);
AVMutableVideoCompositionInstruction * secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set the time range of the second instruction to span the duration of the second video track.
secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration));
AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack];
// Set the transform of the first layer instruction to the preferred transform of the first video track.
[firstVideoLayerInstruction setTransform:firstTransform atTime:kCMTimeZero];
AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack];
// Set the transform of the second layer instruction to the preferred transform of the second video track.
[secondVideoLayerInstruction setTransform:secondTransform atTime:firstVideoAssetTrack.timeRange.duration];
firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction];
secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction];
AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition];
mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction];

Все AVAssetTrack объекты имеют a preferredTransform свойство, содержащее информацию об ориентации для той дорожки актива. Это преобразование применяется каждый раз, когда дорожка актива выведена на экран на экране. В предыдущем коде преобразование инструкции уровня установлено в преобразование дорожки актива так, чтобы видео в новом составе вывело на экран должным образом, как только Вы корректируете его размер рендеринга.

Установка размера рендеринга и продолжительность кадра

Для завершения видео ориентации фиксируют, необходимо корректироваться renderSize свойство соответственно. Необходимо также выбрать подходящее значение для frameDuration свойство, такой как 1/30-й из секунды (или 30 кадров в секунду). По умолчанию, renderScale свойство установлено в 1.0, который является подходящим для этого состава.

CGSize naturalSizeFirst, naturalSizeSecond;
// If the first video asset was shot in portrait mode, then so was the second one if we made it here.
if (isFirstVideoAssetPortrait) {
// Invert the width and height for the video tracks to ensure that they display properly.
    naturalSizeFirst = CGSizeMake(firstVideoAssetTrack.naturalSize.height, firstVideoAssetTrack.naturalSize.width);
    naturalSizeSecond = CGSizeMake(secondVideoAssetTrack.naturalSize.height, secondVideoAssetTrack.naturalSize.width);
}
else {
// If the videos weren't shot in portrait mode, we can just use their natural sizes.
    naturalSizeFirst = firstVideoAssetTrack.naturalSize;
    naturalSizeSecond = secondVideoAssetTrack.naturalSize;
}
float renderWidth, renderHeight;
// Set the renderWidth and renderHeight to the max of the two videos widths and heights.
if (naturalSizeFirst.width > naturalSizeSecond.width) {
    renderWidth = naturalSizeFirst.width;
}
else {
    renderWidth = naturalSizeSecond.width;
}
if (naturalSizeFirst.height > naturalSizeSecond.height) {
    renderHeight = naturalSizeFirst.height;
}
else {
    renderHeight = naturalSizeSecond.height;
}
mutableVideoComposition.renderSize = CGSizeMake(renderWidth, renderHeight);
// Set the frame duration to an appropriate value (i.e. 30 frames per second for video).
mutableVideoComposition.frameDuration = CMTimeMake(1,30);

Экспорт Состава и Сохранение его к Рулону Камеры

Последний шаг в этом процессе включает экспорт всего состава в единственный видеофайл и сохранение того видео к рулону камеры. Вы используете AVAssetExportSession объект создать новый видеофайл и Вы передаете ему свой желаемый URL для выходного файла. Можно тогда использовать ALAssetsLibrary класс для сохранения получающегося видеофайла к Рулону Камеры.

// Create a static date formatter so we only have to initialize it once.
static NSDateFormatter *kDateFormatter;
if (!kDateFormatter) {
    kDateFormatter = [[NSDateFormatter alloc] init];
    kDateFormatter.dateStyle = NSDateFormatterMediumStyle;
    kDateFormatter.timeStyle = NSDateFormatterShortStyle;
}
// Create the export session with the composition and set the preset to the highest quality.
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mutableComposition presetName:AVAssetExportPresetHighestQuality];
// Set the desired output URL for the file created by the export process.
exporter.outputURL = [[[[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:@YES error:nil] URLByAppendingPathComponent:[kDateFormatter stringFromDate:[NSDate date]]] URLByAppendingPathExtension:CFBridgingRelease(UTTypeCopyPreferredTagWithClass((CFStringRef)AVFileTypeQuickTimeMovie, kUTTagClassFilenameExtension))];
// Set the output file type to be a QuickTime movie.
exporter.outputFileType = AVFileTypeQuickTimeMovie;
exporter.shouldOptimizeForNetworkUse = YES;
exporter.videoComposition = mutableVideoComposition;
// Asynchronously export the composition to a video file and save this file to the camera roll once export completes.
[exporter exportAsynchronouslyWithCompletionHandler:^{
    dispatch_async(dispatch_get_main_queue(), ^{
        if (exporter.status == AVAssetExportSessionStatusCompleted) {
            ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init];
            if ([assetsLibrary videoAtPathIsCompatibleWithSavedPhotosAlbum:exporter.outputURL]) {
                [assetsLibrary writeVideoAtPathToSavedPhotosAlbum:exporter.outputURL completionBlock:NULL];
            }
        }
    });
}];