Как объединить видеоклипы разной ориентации с помощью AVFoundation

Я пытаюсь объединить несколько видеоклипов в один с помощью AVFoundation. Я могу создать одно видео с помощью AVMutableComposition, используя приведенный ниже код.

AVMutableComposition *composition = [AVMutableComposition composition];

AVMutableCompositionTrack *compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];

AVMutableCompositionTrack *compositionAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

CMTime startTime = kCMTimeZero;

/*videoClipPaths is a array of paths of the video clips recorded*/

//for loop to combine clips into a single video
for (NSInteger i=0; i < [videoClipPaths count]; i++) {

    NSString *path = (NSString*)[videoClipPaths objectAtIndex:i];

    NSURL *url = [[NSURL alloc] initFileURLWithPath:path];

    AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
    [url release];

    AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    AVAssetTrack *audioTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];

    //set the orientation
    if(i == 0)
    {
        [compositionVideoTrack setPreferredTransform:videoTrack.preferredTransform];
    }

    ok = [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, [asset duration]) ofTrack:videoTrack atTime:startTime error:nil];
    ok = [compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, [asset duration]) ofTrack:audioTrack atTime:startTime error:nil];

    startTime = CMTimeAdd(startTime, [asset duration]);
}

//export the combined video
NSString *combinedPath = /* path of the combined video*/;

NSURL *url = [[NSURL alloc] initFileURLWithPath: combinedPath];

AVAssetExportSession *exporter = [[[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPreset640x480] autorelease];

exporter.outputURL = url;
[url release];

exporter.outputFileType = [[exporter supportedFileTypes] objectAtIndex:0];

[exporter exportAsynchronouslyWithCompletionHandler:^(void){[self combineVideoFinished:exporter.outputURL status:exporter.status error:exporter.error];}];

Приведенный выше код отлично работает, если все видеоклипы были записаны в одной ориентации (книжная или альбомная). Однако, если у меня разные ориентации клипов, часть финального видео будет повернута на 90 градусов вправо (или влево).

Мне было интересно, есть ли способ преобразовать все клипы в одинаковую ориентацию (например, ориентацию первого клипа) при их создании. Судя по тому, что я прочитал из документа XCode, AVMutableVideoCompositionLayerInstruction, похоже, можно использовать для преобразования AVAsset, но я не уверен, как создать и применить несколько разных инструкций по слоям к соответствующим клипам и использовать их в композиции (AVMutableComposition*)

Любая помощь будет оценена по достоинству!


person Song    schedule 04.07.2011    source источник


Ответы (2)


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

AVMutableComposition *composition = [AVMutableComposition composition];    
AVMutableCompositionTrack *compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition]; 
videoComposition.frameDuration = CMTimeMake(1,30); 
videoComposition.renderScale = 1.0;

AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTrack];

// Get only paths the user selected NSMutableArray *array = [NSMutableArray array]; for(NSString* string in videoPathArray){
if(![string isEqualToString:@""]){
    [array addObject:string];
} 

self.videoPathArray = array;

float time = 0;

for (int i = 0; i<self.videoPathArray.count; i++) {

    AVURLAsset *sourceAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:[videoPathArray objectAtIndex:i]] options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:AVURLAssetPreferPreciseDurationAndTimingKey]];

    NSError *error = nil;

    BOOL ok = NO;
    AVAssetTrack *sourceVideoTrack = [[sourceAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];

    CGSize temp = CGSizeApplyAffineTransform(sourceVideoTrack.naturalSize, sourceVideoTrack.preferredTransform);
    CGSize size = CGSizeMake(fabsf(temp.width), fabsf(temp.height));
    CGAffineTransform transform = sourceVideoTrack.preferredTransform;

    videoComposition.renderSize = sourceVideoTrack.naturalSize;
    if (size.width > size.height) {
        [layerInstruction setTransform:transform atTime:CMTimeMakeWithSeconds(time, 30)];
    } else {

        float s = size.width/size.height;

        CGAffineTransform new = CGAffineTransformConcat(transform, CGAffineTransformMakeScale(s,s));

        float x = (size.height - size.width*s)/2;

        CGAffineTransform newer = CGAffineTransformConcat(new, CGAffineTransformMakeTranslation(x, 0));

        [layerInstruction setTransform:newer atTime:CMTimeMakeWithSeconds(time, 30)];
    }

    ok = [compositionVideoTrack insertTimeRange:sourceVideoTrack.timeRange ofTrack:sourceVideoTrack atTime:[composition duration] error:&error];

    if (!ok) {
        // Deal with the error.
        NSLog(@"something went wrong");
    }

    NSLog(@"\n source asset duration is %f \n source vid track timerange is %f %f \n composition duration is %f \n composition vid track time range is %f %f",CMTimeGetSeconds([sourceAsset duration]), CMTimeGetSeconds(sourceVideoTrack.timeRange.start),CMTimeGetSeconds(sourceVideoTrack.timeRange.duration),CMTimeGetSeconds([composition duration]), CMTimeGetSeconds(compositionVideoTrack.timeRange.start),CMTimeGetSeconds(compositionVideoTrack.timeRange.duration));

    time += CMTimeGetSeconds(sourceVideoTrack.timeRange.duration);
}

instruction.layerInstructions = [NSArray arrayWithObject:layerInstruction];
instruction.timeRange = compositionVideoTrack.timeRange; 
videoComposition.instructions = [NSArray arrayWithObject:instruction];

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

person bogardon    schedule 07.07.2011
comment
похоже, работает, однако я не столкнулся с проблемой, связанной с преобразованиями, применяемыми позже - person Song; 19.07.2011
comment
Странный. Я не пытаюсь объединить два файла, я просто пытаюсь получить AVAssetExportSession для сохранения ориентации видео. Вы должны иметь возможность просто позвонить [compositionVideoTrack setPreferredTransform:transform], но это не работает. Использование твоего метода у меня тоже не сработало. Но использование обоих действительно сработало. Похоже на ошибку фреймворка. Я также использую настройку сеанса экспорта AVAssetExportPresetPassthrough. - person Dex; 07.03.2012
comment
У меня также очень странные проблемы со сроками. Чаще всего обрезается последняя секунда или около того. - person Dex; 07.03.2012
comment
@Dex, то же самое для меня с точки зрения настройки обоих преобразований (не проблема времени). - person kevlar; 30.03.2012
comment
@bogardon, когда я объединяю одно портретное видео и одно альбомное видео, я не вижу никаких преобразований в объединенных видео, они объединяются в соответствии с их исходной ориентацией. Подскажите, пожалуйста, как преобразовать видео портрета в альбомную ориентацию? - person Vinod ram; 25.02.2013
comment
это смешать два видео или объединить два видео? - person Allan; 23.09.2015
comment
@bogardon: вы можете объяснить, как сочетаются композиция и композиция видео? Вы как-нибудь накладываете видео композицию на саму композицию? - person Julian F. Weinert; 23.10.2015
comment
Спасибо за ответ +1 - person Salman Khakwani; 17.06.2016
comment
При использовании этого решения видео теряет свой звук, стоит ли добавлять AVMutableCompositionTrack? Не могли бы вы показать, что это возможно? - person B K; 14.02.2017

Вот ответ @bogardon в быстром 4+

 import ARKit

class ARKitSampleViewController: UIViewController {
    var label: UILabel?
    var planeFound = false

    func plane(from anchor: ARPlaneAnchor?) -> SCNNode? {
        let plane = SCNPlane(width: CGFloat(anchor?.extent.x ?? 0.0), height: CGFloat(anchor?.extent.z ?? 0.0))

        plane.firstMaterial?.diffuse.contents = UIColor.clear
        let planeNode = SCNNode(geometry: plane)
        planeNode.position = SCNVector3Make(anchor?.center.x ?? 0.0, 0, anchor?.center.z ?? 0.0)
        // SCNPlanes are vertically oriented in their local coordinate space.
        // Rotate it to match the horizontal orientation of the ARPlaneAnchor.
        planeNode.transform = SCNMatrix4MakeRotation(-.pi * 0.5, 1, 0, 0)

        return planeNode
    }

// MARK: -  ARSCNViewDelegate
    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        if planeFound == false {
            if (anchor is ARPlaneAnchor) {
                DispatchQueue.main.async(execute: {
                    self.planeFound = true
                    self.label?.text = "DANCEFLOOR FOUND. LET'S BOOGIE"

                    let overlay = UIView(frame: self.view.frame)
                    overlay.backgroundColor = UIColor.black
                    overlay.alpha = 0
                    if let label = self.label {
                        self.view.insertSubview(overlay, belowSubview: label)
                    }

                    UIView.animate(withDuration: 1.5, delay: 2, options: .curveEaseIn, animations: {
                        self.label?.alpha = 0
                        overlay.alpha = 0.5
                    }) { finished in
                        let planeAnchor = anchor as? ARPlaneAnchor
                        // Show the disco ball here
                    }
                })
            }
        }
    }
}
person Community    schedule 11.06.2019
comment
Я думаю, вы ввели неправильный код. Код, который вы вставили ... не имеет ничего общего с ответом @bogardon - person p10ben; 04.08.2019