如何在 IOS 中制作慢动作视频

Posted

技术标签:

【中文标题】如何在 IOS 中制作慢动作视频【英文标题】:How to do Slow Motion video in IOS 【发布时间】:2013-06-22 05:31:43 【问题描述】:

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

Ref: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

从上面的链接复制粘贴:

" 编辑 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]);
        
    ];

【问题讨论】:

谁能帮帮我? 如果需要更多信息,请询问我。 很高兴看到有关如何减慢视频和音频速度的答案,但是您如何使音频中的音高也发生变化?你发现了吗? 【参考方案1】:

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

【讨论】:

感谢您的回复。 ios 的 ffmpeg 功能/api 是否有任何限制? 对于你的情况,据我所知没有限制。 您或任何人可以向我推荐一些代码示例或参考链接吗?我的互联网搜索几乎无能为力。 AVFoundation 框架优于提取帧,因为它易于使用,也适用于慢动作请检查上面的代码,我已经编写它工作正常【参考方案2】:

您可以使用 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)

【讨论】:

感谢您的回复。视频缩放工作正常。但音频被静音。正如你提到的那样实现了音轨,但它对我不起作用,我将代码粘贴在这里:pastebin.com/UN3mtpH9。你能检查我的代码,让我知道我做错了什么吗? @vrmarks - 请告诉我如何在慢动作视频中添加音轨? 能否请您告诉我如何添加具有刻度时间范围的音频。因为音频没有变慢,所以正在播放原始音频。当我使用下面的代码时。 AVMutableCompositionTrack compositionCommentaryTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio;[compositionCommentaryTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, videoDuration) toDuration:CMTimeMake(videoDuration.valuevideoScaleFactor, videoDuration.timescale)]; 抱歉回复晚了。我无法使 AVFoundation/AVMutableTrack 使用方法 scaleTimeRange:toDuration: 来缩放音频。一种可能的解决方案是使用带有预设名称:AVAssetExportPresetAppleM4A 和 outputFileType:AVFileTypeAppleM4A 的 AVAssetExportSession 导出音轨。然后使用其他 api 减慢音频并将更新的音轨添加回视频。 @2vision2 dirac.dspdimension.com/Dirac3_Technology_Home_Page/License.html 最后 4 个免费许可证类型。示例 Dirac3-Mobile/Time Stretching 示例它在 Documents 文件夹中生成减慢的音频文件(检查 iPhoneTestAppDelegate.mm 中的因素:107 时间)【参考方案3】:

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

 - (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.");



【讨论】:

当 videoScaleFactor 小于 1 时,它不能在 IOS 9.1 中合成视频。有什么想法吗? 应该至少有一个视频,否则 AVmutable composer 将无法获得任何资产并且会导致应用崩溃 @objectiveCoder 你的代码很棒,帮助我解决了这个问题,但是使用这种方法导出时音频音高似乎没有改变。您是否可以将任何属性添加到导出中,使其行为类似于您在播放器项上设置 asset.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmVarispeed 时的行为? 知道如何在Photos 框架中使用PHAsset 来实现这一点吗? 有什么方法可以将视频转换为快速通道的快速动作吗?真的需要帮助【参考方案4】:

swift中的一个例子:

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).")


【讨论】:

你知道如何让音频的音调也随着速度下降吗?音频变慢,但音调不跟随。 scaleTimeRange(...) 方法不支持音高转换。所以减慢的音频的音调不会跟随,但我想你知道。许多人为此推荐了狄拉克。您可能可以对此进行一些研究。我还没有和狄拉克合作过,所以我不知道它是如何工作的。祝你好运!【参考方案5】:

带或不带音轨的慢 + 快

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

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

我开设了一个课程,可以帮助您从AVAsset 生成slower 视频。 + 点是您也可以将其设为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)

【讨论】:

为什么有 3x 或更多的限制?是否有应用程序可以进一步减慢/加快视频播放速度? @RoiMulia 没有限制它只在我的功能中......您可以根据您的要求使用。 @AnandGautam 不要根据你的要求投反对票。 您的视频网址是什么?你确定它是一个有效的网址吗? @AnandGautam 你给outputFileURL 分配了什么?如果您使用的是设备,则它不能是桌面 URL。您应该就 SO 提出问题,而不是对工作代码投反对票。【参考方案6】:

斯威夫特 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.")
              
        

【讨论】:

【参考方案7】:

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

这是“我为实现慢动作而创建的代码 sn-p”

如果此代码有效,请给我一个 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
        
    )

【讨论】:

以上是关于如何在 IOS 中制作慢动作视频的主要内容,如果未能解决你的问题,请参考以下文章

使用 Xcode 5 iOS 模拟器制作慢动作动画

vue如何调速度

如何制作适用于 IOS 和桌面浏览器的自定义全屏视频按钮?

如何在 iOS 中制作 OpenGL ES 帧缓冲区的副本?

如何缩放 iOS 绘图应用程序?

如何利用Axure RP 8软件制作中继器动作