【问题标题】:How to do Slow Motion video in IOS如何在 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]);
        }
    }];

【问题讨论】:

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

标签: ios objective-c video ffmpeg avfoundation


【解决方案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
            }
        })
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-30
      • 2013-02-06
      • 2011-09-17
      • 2013-08-04
      • 2016-03-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-15
      相关资源
      最近更新 更多