22

我必须在视频文件中与音频一起在某些帧之间进行“慢动作”,并且需要将渐变视频存储为新视频。

参考:http ://www.youtube.com/watch?v=BJ3_xMGzauk (从 0 到 10 秒观看)

根据我的分析,我发现AVFoundation框架很有帮助。

参考:http: //developer.apple.com/library/ios/#documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/00_Introduction.html

从上面的链接复制并粘贴:

" Editing AV Foundation 使用合成从现有媒体片段(通常是一个或多个视频和音频轨道)创建新资产。您使用可变合成来添加和删除轨道,并调整它们的时间顺序。您还可以设置相对音轨的音量和渐变;以及设置视频轨道的不透明度和不透明度渐变。合成是保存在内存中的媒体片段的集合。使用导出会话导出合成时,它会折叠到文件中。开iOS 4.1 及更高版本,您还可以使用资产编写器从样本缓冲区或静止图像等媒体创建资产。

"

问题: 我可以使用 AVFoundation 框架对视频/音频文件进行“慢动作”吗?或者有没有其他可用的包?如果我想分别处理音频和视频,请指导我怎么做?

更新 :: AV 导出会话代码:

 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *outputURL = paths[0];
    NSFileManager *manager = [NSFileManager defaultManager];
    [manager createDirectoryAtPath:outputURL withIntermediateDirectories:YES attributes:nil error:nil];
    outputURL = [outputURL stringByAppendingPathComponent:@"output.mp4"];
    // Remove Existing File
    [manager removeItemAtPath:outputURL error:nil];
    AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:self.inputAsset presetName:AVAssetExportPresetLowQuality];
    exportSession.outputURL = [NSURL fileURLWithPath:outputURL]; // output path;
    exportSession.outputFileType = AVFileTypeQuickTimeMovie;
    [exportSession exportAsynchronouslyWithCompletionHandler:^(void) {
        if (exportSession.status == AVAssetExportSessionStatusCompleted) {
            [self writeVideoToPhotoLibrary:[NSURL fileURLWithPath:outputURL]];
            ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
            [library writeVideoAtPathToSavedPhotosAlbum:[NSURL fileURLWithPath:outputURL] completionBlock:^(NSURL *assetURL, NSError *error){
                if (error) {
                    NSLog(@"Video could not be saved");
                }
            }];
        } else {
            NSLog(@"error: %@", [exportSession error]);
        }
    }];
4

7 回答 7

41

您可以使用 AVFoundation 和 CoreMedia 框架来缩放视频。看一下 AVMutableCompositionTrack 方法:

- (void)scaleTimeRange:(CMTimeRange)timeRange toDuration:(CMTime)duration;

样本:

AVURLAsset* videoAsset = nil; //self.inputAsset;

//create mutable composition
AVMutableComposition *mixComposition = [AVMutableComposition composition];

AVMutableCompositionTrack *compositionVideoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo
                                                                               preferredTrackID:kCMPersistentTrackID_Invalid];
NSError *videoInsertError = nil;
BOOL videoInsertResult = [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration)
                                                        ofTrack:[[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]
                                                         atTime:kCMTimeZero
                                                          error:&videoInsertError];
if (!videoInsertResult || nil != videoInsertError) {
    //handle error
    return;
}

//slow down whole video by 2.0
double videoScaleFactor = 2.0;
CMTime videoDuration = videoAsset.duration;

[compositionVideoTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, videoDuration)
                           toDuration:CMTimeMake(videoDuration.value*videoScaleFactor, videoDuration.timescale)];

//export
AVAssetExportSession* assetExport = [[AVAssetExportSession alloc] initWithAsset:mixComposition
                                                                     presetName:AVAssetExportPresetLowQuality];

(可能来自 videoAsset 的音轨也应该添加到 mixComposition)

于 2013-07-08T13:51:01.743 回答
14

较慢 + 较快,有或没有音轨

我已经尝试并能够减慢资产。

compositionVideoTrack?.scaleTimeRange(timeRange, toDuration: scaledVideoDuration)成功了。

我做了一个课程,可以帮助您slowerAVAsset. + 点是你也可以制作它faster,另一个 + 点是它也会处理音频。

这是我的自定义类示例:

import UIKit
import AVFoundation

enum SpeedoMode {
    case Slower
    case Faster
}

class VSVideoSpeeder: NSObject {

    /// Singleton instance of `VSVideoSpeeder`
    static var shared: VSVideoSpeeder = {
       return VSVideoSpeeder()
    }()

    /// Range is b/w 1x, 2x and 3x. Will not happen anything if scale is out of range. Exporter will be nil in case url is invalid or unable to make asset instance.
    func scaleAsset(fromURL url: URL,  by scale: Int64, withMode mode: SpeedoMode, completion: @escaping (_ exporter: AVAssetExportSession?) -> Void) {

        /// Check the valid scale
        if scale < 1 || scale > 3 {
            /// Can not proceed, Invalid range
            completion(nil)
            return
        }

        /// Asset
        let asset = AVAsset(url: url)

        /// Video Tracks
        let videoTracks = asset.tracks(withMediaType: AVMediaType.video)
        if videoTracks.count == 0 {
            /// Can not find any video track
            completion(nil)
            return
        }

        /// Get the scaled video duration
        let scaledVideoDuration = (mode == .Faster) ? CMTimeMake(asset.duration.value / scale, asset.duration.timescale) : CMTimeMake(asset.duration.value * scale, asset.duration.timescale)
        let timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration)

        /// Video track
        let videoTrack = videoTracks.first!

        let mixComposition = AVMutableComposition()
        let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)

        /// Audio Tracks
        let audioTracks = asset.tracks(withMediaType: AVMediaType.audio)
        if audioTracks.count > 0 {
            /// Use audio if video contains the audio track
            let compositionAudioTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)

            /// Audio track
            let audioTrack = audioTracks.first!
            do {
                try compositionAudioTrack?.insertTimeRange(timeRange, of: audioTrack, at: kCMTimeZero)
                compositionAudioTrack?.scaleTimeRange(timeRange, toDuration: scaledVideoDuration)
            } catch _ {
                /// Ignore audio error
            }
        }

        do {
            try compositionVideoTrack?.insertTimeRange(timeRange, of: videoTrack, at: kCMTimeZero)
            compositionVideoTrack?.scaleTimeRange(timeRange, toDuration: scaledVideoDuration)

            /// Keep original transformation
            compositionVideoTrack?.preferredTransform = videoTrack.preferredTransform

            /// Initialize Exporter now
            let outputFileURL = URL(fileURLWithPath: "/Users/thetiger/Desktop/scaledVideo.mov")
           /// Note:- Please use directory path if you are testing with device.

            if FileManager.default.fileExists(atPath: outputFileURL.absoluteString) {
                try FileManager.default.removeItem(at: outputFileURL)
            }

            let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality)
            exporter?.outputURL = outputFileURL
            exporter?.outputFileType = AVFileType.mov
            exporter?.shouldOptimizeForNetworkUse = true
            exporter?.exportAsynchronously(completionHandler: {
                completion(exporter)
            })

        } catch let error {
            print(error.localizedDescription)
            completion(nil)
            return
        }
    }

}

我将 1x、2x 和 3x 作为有效比例。类包含正确的验证和处理。以下是如何使用此功能的示例。

let url = Bundle.main.url(forResource: "1", withExtension: "mp4")!
VSVideoSpeeder.shared.scaleAsset(fromURL: url, by: 3, withMode: SpeedoMode.Slower) { (exporter) in
     if let exporter = exporter {
         switch exporter.status {
                case .failed: do {
                      print(exporter.error?.localizedDescription ?? "Error in exporting..")
                }
                case .completed: do {
                      print("Scaled video has been generated successfully!")
                }
                case .unknown: break
                case .waiting: break
                case .exporting: break
                case .cancelled: break
           }
      }
      else {
           /// Error
           print("Exporter is not initialized.")
      }
}

此行将处理音频缩放

compositionAudioTrack?.scaleTimeRange(timeRange, toDuration: scaledVideoDuration)

于 2018-02-02T10:52:44.203 回答
10

我已经实现了在视频中添加慢动作,包括音频以及正确的输出方向。

 - (void)SlowMotion:(NSURL *)URl
 {
   AVURLAsset* videoAsset = [AVURLAsset URLAssetWithURL:URl options:nil]; //self.inputAsset;

AVAsset *currentAsset = [AVAsset assetWithURL:URl];
AVAssetTrack *vdoTrack = [[currentAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
//create mutable composition
AVMutableComposition *mixComposition = [AVMutableComposition composition];

AVMutableCompositionTrack *compositionVideoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *compositionAudioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

NSError *videoInsertError = nil;
BOOL videoInsertResult = [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration)
                                                        ofTrack:[[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]
                                                         atTime:kCMTimeZero
                                                          error:&videoInsertError];
if (!videoInsertResult || nil != videoInsertError) {
    //handle error
    return;
}

NSError *audioInsertError =nil;
BOOL audioInsertResult =[compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration)
                                                       ofTrack:[[currentAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0]
                                                        atTime:kCMTimeZero
                                                         error:&audioInsertError];

if (!audioInsertResult || nil != audioInsertError) {
    //handle error
    return;
}

CMTime duration =kCMTimeZero;
duration=CMTimeAdd(duration, currentAsset.duration);
//slow down whole video by 2.0
double videoScaleFactor = 2.0;
CMTime videoDuration = videoAsset.duration;

[compositionVideoTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, videoDuration)
                           toDuration:CMTimeMake(videoDuration.value*videoScaleFactor, videoDuration.timescale)];
[compositionAudioTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, videoDuration)
                           toDuration:CMTimeMake(videoDuration.value*videoScaleFactor, videoDuration.timescale)];
[compositionVideoTrack setPreferredTransform:vdoTrack.preferredTransform];

        NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *docsDir = [dirPaths objectAtIndex:0];
        NSString *outputFilePath = [docsDir stringByAppendingPathComponent:[NSString stringWithFormat:@"slowMotion.mov"]];
        if ([[NSFileManager defaultManager] fileExistsAtPath:outputFilePath])
        [[NSFileManager defaultManager] removeItemAtPath:outputFilePath error:nil];
        NSURL *_filePath = [NSURL fileURLWithPath:outputFilePath];

//export
AVAssetExportSession* assetExport = [[AVAssetExportSession alloc] initWithAsset:mixComposition
                                                                        presetName:AVAssetExportPresetLowQuality];
assetExport.outputURL=_filePath;
                          assetExport.outputFileType =           AVFileTypeQuickTimeMovie;
  exporter.shouldOptimizeForNetworkUse = YES;
                           [assetExport exportAsynchronouslyWithCompletionHandler:^
                            {

                                switch ([assetExport status]) {
                                    case AVAssetExportSessionStatusFailed:
                                    {
                                        NSLog(@"Export session faiied with error: %@", [assetExport error]);
                                        dispatch_async(dispatch_get_main_queue(), ^{
                                            // completion(nil);
                                        });
                                    }
                                        break;
                                    case AVAssetExportSessionStatusCompleted:
                                    {

                                        NSLog(@"Successful");
                                        NSURL *outputURL = assetExport.outputURL;

                                        ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
                                        if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:outputURL]) {

                                            [self writeExportedVideoToAssetsLibrary:outputURL];
                                        }
                                        dispatch_async(dispatch_get_main_queue(), ^{
                                            //                                            completion(_filePath);
                                        });

                                    }
                                        break;
                                    default:

                                        break;
                                }


                            }];


 }

  - (void)writeExportedVideoToAssetsLibrary :(NSURL *)url {
NSURL *exportURL = url;
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:exportURL]) {
    [library writeVideoAtPathToSavedPhotosAlbum:exportURL completionBlock:^(NSURL *assetURL, NSError *error){
        dispatch_async(dispatch_get_main_queue(), ^{
            if (error) {
                UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:[error localizedDescription]
                                                                    message:[error localizedRecoverySuggestion]
                                                                   delegate:nil
                                                          cancelButtonTitle:@"OK"
                                                          otherButtonTitles:nil];
                [alertView show];
            }
            if(!error)
            {
               // [activityView setHidden:YES];
                UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Sucess"
                                                                    message:@"video added to gallery successfully"
                                                                   delegate:nil
                                                          cancelButtonTitle:@"OK"
                                                          otherButtonTitles:nil];
                [alertView show];
            }
 #if !TARGET_IPHONE_SIMULATOR
            [[NSFileManager defaultManager] removeItemAtURL:exportURL error:nil];
#endif
        });
    }];
} else {
    NSLog(@"Video could not be exported to assets library.");
}

}
于 2015-11-25T10:52:53.490 回答
2

我会使用ffmpeg从初始视频中提取所有帧,然后使用 AVAssetWriter 收集在一起,但帧速率较低。为了获得更饱满的慢动作,您可能需要应用一些模糊效果,甚至在现有帧之间生成帧,这将由两帧混合而成。

于 2013-07-04T22:07:39.933 回答
2

快速的一个例子:

一世

var asset: AVAsset?  
func configureAssets(){

    let videoAsset = AVURLAsset(url: Bundle.main.url(forResource: "sample", withExtension: "m4v")!)
    let audioAsset = AVURLAsset(url: Bundle.main.url(forResource: "sample", withExtension: "m4a")!)
    //    let audioAsset2 = AVURLAsset(url: Bundle.main.url(forResource: "audio2", withExtension: "m4a")!)

    let comp = AVMutableComposition()

    let videoAssetSourceTrack = videoAsset.tracks(withMediaType: AVMediaTypeVideo).first! as AVAssetTrack
    let audioAssetSourceTrack = videoAsset.tracks(withMediaType: AVMediaTypeAudio).first! as AVAssetTrack
    //    let audioAssetSourceTrack2 = audioAsset2.tracks(withMediaType: AVMediaTypeAudio).first! as AVAssetTrack

    let videoCompositionTrack = comp.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: kCMPersistentTrackID_Invalid)
    let audioCompositionTrack = comp.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: kCMPersistentTrackID_Invalid)

    do {

        try videoCompositionTrack.insertTimeRange(
            CMTimeRangeMake(kCMTimeZero, CMTimeMakeWithSeconds(9 , 600)),
            of: videoAssetSourceTrack,
            at: kCMTimeZero)



        try audioCompositionTrack.insertTimeRange(
            CMTimeRangeMake(kCMTimeZero, CMTimeMakeWithSeconds(9, 600)),
            of: audioAssetSourceTrack,
            at: kCMTimeZero)

        //
        //      try audioCompositionTrack.insertTimeRange(
        //        CMTimeRangeMake(kCMTimeZero, CMTimeMakeWithSeconds(3, 600)),
        //        of: audioAssetSourceTrack2,
        //        at: CMTimeMakeWithSeconds(7, 600))

        let videoScaleFactor = Int64(2.0)
        let videoDuration: CMTime = videoAsset.duration


        videoCompositionTrack.scaleTimeRange(CMTimeRangeMake(kCMTimeZero, videoDuration), toDuration: CMTimeMake(videoDuration.value * videoScaleFactor, videoDuration.timescale))
        audioCompositionTrack.scaleTimeRange(CMTimeRangeMake(kCMTimeZero, videoDuration), toDuration: CMTimeMake(videoDuration.value * videoScaleFactor, videoDuration.timescale))
        videoCompositionTrack.preferredTransform = videoAssetSourceTrack.preferredTransform



    }catch { print(error) }

    asset = comp
}

  func createFileFromAsset(_ asset: AVAsset){

let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL

let filePath = documentsDirectory.appendingPathComponent("rendered-audio.m4v")
deleteFile(filePath)

if let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetLowQuality){


  exportSession.canPerformMultiplePassesOverSourceMediaData = true
  exportSession.outputURL = filePath
  exportSession.timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration)
  exportSession.outputFileType = AVFileTypeQuickTimeMovie
  exportSession.exportAsynchronously {
    _ in
    print("finished: \(filePath) :  \(exportSession.status.rawValue) ")
  }
}

 }

 func deleteFile(_ filePath:URL) {
guard FileManager.default.fileExists(atPath: filePath.path) else {
  return
}

do {
  try FileManager.default.removeItem(atPath: filePath.path)
}catch{
  fatalError("Unable to delete file: \(error) : \(#function).")
}
}
于 2017-01-17T10:28:06.753 回答
0

斯威夫特 5

这是@TheTiger 转换为 SwiftUI 的代码:

import UIKit
import AVFoundation


    enum SpeedoMode {
        case Slower
        case Faster
    }

    class VSVideoSpeeder: NSObject {

        /// Singleton instance of `VSVideoSpeeder`
        static var shared: VSVideoSpeeder = {
           return VSVideoSpeeder()
        }()

        /// Range is b/w 1x, 2x and 3x. Will not happen anything if scale is out of range. Exporter will be nil in case url is invalid or unable to make asset instance.
        func scaleAsset(fromURL url: URL,  by scale: Int64, withMode mode: SpeedoMode, completion: @escaping (_ exporter: AVAssetExportSession?) -> Void) {

            /// Check the valid scale
            if scale < 1 || scale > 3 {
                /// Can not proceed, Invalid range
                completion(nil)
                return
            }

            /// Asset
            let asset = AVAsset(url: url)

            /// Video Tracks
            let videoTracks = asset.tracks(withMediaType: AVMediaType.video)
            if videoTracks.count == 0 {
                /// Can not find any video track
                completion(nil)
                return
            }

            /// Get the scaled video duration
            let scaledVideoDuration = (mode == .Faster) ? CMTimeMake(value: asset.duration.value / scale, timescale: asset.duration.timescale) : CMTimeMake(value: asset.duration.value * scale, timescale: asset.duration.timescale)
            let timeRange = CMTimeRangeMake(start: CMTime.zero, duration: asset.duration)

            /// Video track
            let videoTrack = videoTracks.first!

            let mixComposition = AVMutableComposition()
            let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)

            /// Audio Tracks
            let audioTracks = asset.tracks(withMediaType: AVMediaType.audio)
            if audioTracks.count > 0 {
                /// Use audio if video contains the audio track
                let compositionAudioTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)

                /// Audio track
                let audioTrack = audioTracks.first!
                do {
                    try compositionAudioTrack?.insertTimeRange(timeRange, of: audioTrack, at: CMTime.zero)
                    compositionAudioTrack?.scaleTimeRange(timeRange, toDuration: scaledVideoDuration)
                } catch _ {
                    /// Ignore audio error
                }
            }

            do {
                try compositionVideoTrack?.insertTimeRange(timeRange, of: videoTrack, at: CMTime.zero)
                compositionVideoTrack?.scaleTimeRange(timeRange, toDuration: scaledVideoDuration)

                /// Keep original transformation
                compositionVideoTrack?.preferredTransform = videoTrack.preferredTransform

                /// Initialize Exporter now
                let outputFileURL = URL(fileURLWithPath: "/Users/thetiger/Desktop/scaledVideo.mov")
               /// Note:- Please use directory path if you are testing with device.

                if FileManager.default.fileExists(atPath: outputFileURL.absoluteString) {
                    try FileManager.default.removeItem(at: outputFileURL)
                }

                let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality)
                exporter?.outputURL = outputFileURL
                exporter?.outputFileType = AVFileType.mov
                exporter?.shouldOptimizeForNetworkUse = true
                exporter?.exportAsynchronously(completionHandler: {
                    completion(exporter)
                })

            } catch let error {
                print(error.localizedDescription)
                completion(nil)
                return
            }
        }

    }

}

使用相同的用例:

        let url = Bundle.main.url(forResource: "1", withExtension: "mp4")!
        VSVideoSpeeder.shared.scaleAsset(fromURL: url, by: 3, withMode: SpeedoMode.Slower) { (exporter) in
             if let exporter = exporter {
                 switch exporter.status {
                        case .failed: do {
                              print(exporter.error?.localizedDescription ?? "Error in exporting..")
                        }
                        case .completed: do {
                              print("Scaled video has been generated successfully!")
                        }
                        case .unknown: break
                        case .waiting: break
                        case .exporting: break
                        case .cancelled: break
                   }
              }
              else {
                   /// Error
                   print("Exporter is not initialized.")
              }
        }
于 2020-06-06T17:50:45.690 回答
0

在 iOS swift 中创建“慢动作”视频并不容易,我遇到了许多“慢动作”,他们知道它们不起作用,或者其中的一些代码已被贬值。所以我终于找到了一种在 Swift 中制作慢动作的方法。注意:此代码可用于 120fps 也大于此。您可以像我一样制作慢动作音频

这是“我为实现慢动作而创建的代码片段”

如果此代码有效,请给我一个 UPVOTE。

    func slowMotion(pathUrl: URL) {

    let videoAsset = AVURLAsset.init(url: pathUrl, options: nil)
    let currentAsset = AVAsset.init(url: pathUrl)

    let vdoTrack = currentAsset.tracks(withMediaType: .video)[0]
    let mixComposition = AVMutableComposition()

    let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)

    let videoInsertError: Error? = nil
    var videoInsertResult = false
    do {
        try compositionVideoTrack?.insertTimeRange(
            CMTimeRangeMake(start: .zero, duration: videoAsset.duration),
            of: videoAsset.tracks(withMediaType: .video)[0],
            at: .zero)
        videoInsertResult = true
    } catch let videoInsertError {
    }

    if !videoInsertResult || videoInsertError != nil {
        //handle error
        return
    }


    var duration: CMTime = .zero
    duration = CMTimeAdd(duration, currentAsset.duration)
    
    
    //MARK: You see this constant (videoScaleFactor) this helps in achieving the slow motion that you wanted. This increases the time scale of the video that makes slow motion
    // just increase the videoScaleFactor value in order to play video in higher frames rates(more slowly)
    let videoScaleFactor = 2.0
    let videoDuration = videoAsset.duration
    
    compositionVideoTrack?.scaleTimeRange(
        CMTimeRangeMake(start: .zero, duration: videoDuration),
        toDuration: CMTimeMake(value: videoDuration.value * Int64(videoScaleFactor), timescale: videoDuration.timescale))
    compositionVideoTrack?.preferredTransform = vdoTrack.preferredTransform
    
    let dirPaths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).map(\.path)
    let docsDir = dirPaths[0]
    let outputFilePath = URL(fileURLWithPath: docsDir).appendingPathComponent("slowMotion\(UUID().uuidString).mp4").path
    
    if FileManager.default.fileExists(atPath: outputFilePath) {
        do {
            try FileManager.default.removeItem(atPath: outputFilePath)
        } catch {
        }
    }
    let filePath = URL(fileURLWithPath: outputFilePath)
    
    let assetExport = AVAssetExportSession(
        asset: mixComposition,
        presetName: AVAssetExportPresetHighestQuality)
    assetExport?.outputURL = filePath
    assetExport?.outputFileType = .mp4
    
    assetExport?.exportAsynchronously(completionHandler: {
        switch assetExport?.status {
        case .failed:
            print("asset output media url = \(String(describing: assetExport?.outputURL))")
            print("Export session faiied with error: \(String(describing: assetExport?.error))")
            DispatchQueue.main.async(execute: {
                // completion(nil);
            })
        case .completed:
            print("Successful")
            let outputURL = assetExport!.outputURL
            print("url path = \(String(describing: outputURL))")
            
            PHPhotoLibrary.shared().performChanges({
                PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL!)
            }) { saved, error in
                if saved {
                    print("video successfully saved in photos gallery view video in photos gallery")
                }
                if (error != nil) {
                    print("error in saing video \(String(describing: error?.localizedDescription))")
                }
            }
            DispatchQueue.main.async(execute: {
                //      completion(_filePath);
            })
        case .none:
            break
        case .unknown:
            break
        case .waiting:
            break
        case .exporting:
            break
        case .cancelled:
            break
        case .some(_):
            break
        }
    })
}
于 2021-05-10T03:55:21.890 回答