ios - iOS 中的视频编辑问题

标签 ios avfoundation video-editing avasset

我目前正在开发一个 iOS 应用程序,它可以合并所需数量的视频。一旦用户点击按钮合并视频,视频就会被合并,然后使用 AVPlayer 播放:

CMTime nextClipStartTime = kCMTimeZero;
NSInteger i;
CMTime transitionDuration = CMTimeMake(1, 1); // Default transition duration is one second.

// Add two video tracks and two audio tracks.
AVMutableCompositionTrack *compositionVideoTracks[2];
AVMutableCompositionTrack *compositionAudioTracks[2];
compositionVideoTracks[0] = [self.mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
compositionVideoTracks[1] = [self.mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
compositionAudioTracks[0] = [self.mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
compositionAudioTracks[1] = [self.mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

CMTimeRange *passThroughTimeRanges = alloca(sizeof(CMTimeRange) * [self.selectedAssets count]);
CMTimeRange *transitionTimeRanges = alloca(sizeof(CMTimeRange) * [self.selectedAssets count]);

// Place clips into alternating video & audio tracks in composition, overlapped by transitionDuration.
for (i = 0; i < [self.selectedAssets count]; i++ )
{
    NSInteger alternatingIndex = i % 2; // alternating targets: 0, 1, 0, 1, ...
    AVURLAsset *asset = [self.selectedAssets objectAtIndex:i];

    NSLog(@"number of tracks %d",asset.tracks.count);

    CMTimeRange assetTimeRange;
    assetTimeRange.start = kCMTimeZero;
    assetTimeRange.duration = asset.duration;
    NSValue *clipTimeRange = [NSValue valueWithCMTimeRange:assetTimeRange];
    CMTimeRange timeRangeInAsset;
    if (clipTimeRange)
        timeRangeInAsset = [clipTimeRange CMTimeRangeValue];
    else
        timeRangeInAsset = CMTimeRangeMake(kCMTimeZero, [asset duration]);

    AVAssetTrack *clipVideoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    [compositionVideoTracks[alternatingIndex] insertTimeRange:timeRangeInAsset ofTrack:clipVideoTrack atTime:nextClipStartTime error:nil];

    AVAssetTrack *clipAudioTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
    [compositionAudioTracks[alternatingIndex] insertTimeRange:timeRangeInAsset ofTrack:clipAudioTrack atTime:nextClipStartTime error:nil];

    // Remember the time range in which this clip should pass through.
    // Every clip after the first begins with a transition.
    // Every clip before the last ends with a transition.
    // Exclude those transitions from the pass through time ranges.
    passThroughTimeRanges[i] = CMTimeRangeMake(nextClipStartTime, timeRangeInAsset.duration);
    if (i > 0) {
        passThroughTimeRanges[i].start = CMTimeAdd(passThroughTimeRanges[i].start, transitionDuration);
        passThroughTimeRanges[i].duration = CMTimeSubtract(passThroughTimeRanges[i].duration, transitionDuration);
    }
    if (i+1 < [self.selectedAssets count]) {
        passThroughTimeRanges[i].duration = CMTimeSubtract(passThroughTimeRanges[i].duration, transitionDuration);
    }

    // The end of this clip will overlap the start of the next by transitionDuration.
    // (Note: this arithmetic falls apart if timeRangeInAsset.duration < 2 * transitionDuration.)
    nextClipStartTime = CMTimeAdd(nextClipStartTime, timeRangeInAsset.duration);
    nextClipStartTime = CMTimeSubtract(nextClipStartTime, transitionDuration);

    // Remember the time range for the transition to the next item.
    transitionTimeRanges[i] = CMTimeRangeMake(nextClipStartTime, transitionDuration);
}

// Set up the video composition if we are to perform crossfade or push transitions between clips.
NSMutableArray *instructions = [NSMutableArray array];

// Cycle between "pass through A", "transition from A to B", "pass through B", "transition from B to A".
for (i = 0; i < [self.selectedAssets count]; i++ )
{
    NSInteger alternatingIndex = i % 2; // alternating targets

    // Pass through clip i.
    AVMutableVideoCompositionInstruction *passThroughInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    passThroughInstruction.timeRange = passThroughTimeRanges[i];
    AVMutableVideoCompositionLayerInstruction *passThroughLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTracks[alternatingIndex]];

    passThroughInstruction.layerInstructions = [NSArray arrayWithObject:passThroughLayer];
    [instructions addObject:passThroughInstruction];

    AVMutableVideoCompositionLayerInstruction *fromLayer;

    AVMutableVideoCompositionLayerInstruction *toLayer;

    if (i+1 < [self.selectedAssets count])
    {
        // Add transition from clip i to clip i+1.

        AVMutableVideoCompositionInstruction *transitionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
        transitionInstruction.timeRange = transitionTimeRanges[i];
        fromLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTracks[alternatingIndex]];
        toLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTracks[1-alternatingIndex]];


        // Fade out the fromLayer by setting a ramp from 1.0 to 0.0.
        [fromLayer setOpacityRampFromStartOpacity:1.0 toEndOpacity:0.0 timeRange:transitionTimeRanges[i]];

        transitionInstruction.layerInstructions = [NSArray arrayWithObjects:fromLayer, toLayer, nil];
        [instructions addObject:transitionInstruction];



    }

    AVURLAsset *sourceAsset = [AVURLAsset URLAssetWithURL:[self.selectedItemsURL objectAtIndex:i] options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:AVURLAssetPreferPreciseDurationAndTimingKey]];

    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;

    self.videoComposition.renderSize = sourceVideoTrack.naturalSize;
    if (size.width > size.height) {

        [fromLayer setTransform:transform atTime:sourceAsset.duration];
    } 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));

        [fromLayer setTransform:newer atTime:sourceAsset.duration];
    }



}

self.videoComposition.instructions = instructions;

self.videoComposition.frameDuration = CMTimeMake(1, 30);



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];

self.exporter = [[AVAssetExportSession alloc] initWithAsset:self.mixComposition presetName:AVAssetExportPresetMediumQuality];
self.exporter.outputURL=url;
self.exporter.outputFileType = AVFileTypeQuickTimeMovie;
self.exporter.videoComposition = self.videoComposition;
self.exporter.shouldOptimizeForNetworkUse = YES;

self.playerItem = [AVPlayerItem playerItemWithAsset:self.mixComposition];
self.playerItem.videoComposition = self.videoComposition;
AVPlayer *player = [AVPlayer playerWithPlayerItem:self.playerItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
[playerLayer setFrame:CGRectMake(0, 0, self.imageView.frame.size.width, self.imageView.frame.size.height)];
[[[self imageView] layer] addSublayer:playerLayer];
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[player play];

[[NSNotificationCenter defaultCenter]
 addObserver:self selector:@selector(checkPlayEnded) name:AVPlayerItemDidPlayToEndTimeNotification object:self.playerItem];

我目前面临以下问题:

  1. 如果一个视频是纵向的,而另一个是横向的,我如何才能横向旋转纵向视频,因为我的 View 是横向的,但纵向视频保留其原始状态? (我正在加载存储在相机胶卷中的视频,而不是将它们记录在我的应用程序中)

  2. 忽略上述问题,如果我合并任意数量的视频,它们都可以正常工作。一旦我将该新视频保存在我的库中,然后再次将其加载到我的应用程序中并尝试将该视频与其他一些新视频结合在一起,分辨率就会受到干扰,尽管这两个视频如果在应用程序中单独播放,效果非常好。我该如何解决这个问题?

(我尝试遵循 WWDC 2010 视频编辑教程,因此此代码是从那里提取的。)

最佳答案

在为 AVMutableVideoCompositionInstruction 创建对象时,您可以在上面的代码中检查视频运行时的方向。

要附加到解决问题的代码中的代码是......

AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
instruction.timeRange = CMTimeRangeMake(kCMTimeZero, [mutableComposition duration]);
AVAssetTrack *videoTrack = [[mutableComposition tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];

AVMutableVideoCompositionLayerInstruction * layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];

UIImageOrientation videoAssetOrientation_  = UIImageOrientationUp;
BOOL  isVideoAssetPortrait_  = NO;
CGAffineTransform videoTransform = assetVideoTrack.preferredTransform;

if(videoTransform.a == 0 && videoTransform.b == 1.0 && videoTransform.c == -1.0 && videoTransform.d == 0)
{
    videoAssetOrientation_= UIImageOrientationRight;
    isVideoAssetPortrait_ = YES;
}
if(videoTransform.a == 0 && videoTransform.b == -1.0 && videoTransform.c == 1.0 && videoTransform.d == 0)
{
    videoAssetOrientation_ =  UIImageOrientationLeft;
    isVideoAssetPortrait_ = YES;
}

CGFloat FirstAssetScaleToFitRatio = 320.0 / assetVideoTrack.naturalSize.width;
if(isVideoAssetPortrait_)
{
    videoSize=CGSizeMake(350,400);
    FirstAssetScaleToFitRatio = 320.0/assetVideoTrack.naturalSize.height;
    CGAffineTransform FirstAssetScaleFactor = CGAffineTransformMakeScale(FirstAssetScaleToFitRatio,FirstAssetScaleToFitRatio);
    [layerInstruction setTransform:CGAffineTransformConcat(assetVideoTrack.preferredTransform, FirstAssetScaleFactor) atTime:kCMTimeZero];
}
else
{
    videoSize=CGSizeMake(assetVideoTrack.naturalSize.width,assetVideoTrack.naturalSize.height);
}

上述代码将使横向视频保持横向,并防止视频从纵向转换为横向。

我希望这会有所帮助。而不是首先转换为正确的方向,然后应用编辑。如果您附加此代码,您的一步将会减少,并且可以在一个代码中完成两件事(即编辑和方向),也可以在很多情况下完成更快的方法。

关于ios - iOS 中的视频编辑问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13246557/

相关文章:

iOS,处理未确定的JSON数据结构

ios - 导航栏不出现

iOS 后台音频不播放

video - 如何使用 ffmpeg 提取时间准确的视频片段?

bash - 如何在 Shell 脚本中为 "for"语句使用 2 个变量

ios - 实现可以在 Bolts Framework 中取消的任务 (BFTask)

android - Flutter:如何构建改变移动设备方向时移动 block 的逻辑?

avfoundation - AVPlayer Slider 寻求向后问题

ios - 如何使用 AVFoundation 检测单个 Airpod 被取下?

java - 如何将多个 MP4 视频文件合并为一个文件