我的编程空间,编程开发者的网络收藏夹
学习永远不晚

iOS基于AVFoundation 制作用于剪辑视频项目

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

iOS基于AVFoundation 制作用于剪辑视频项目

最近做了一个剪辑视频的小项目,踩了一些小坑,但还是有惊无险的实现了功能。

其实 Apple 官方也给了一个 UIVideoEditController 让我们来做视频的处理,但难以进行扩展或者自定义,所以咱们就用 Apple 给的一个框架 AVFoundation 来开发自定义的视频处理。

而且发现网上并没有相关的并且比较系统的资料,于是写下了本文,希望能对也在做视频处理方面的新手(比如我)能带来帮助。

项目效果图

项目的功能大概就是对视频轨道的撤销、分割、删除还有拖拽视频块来对视频扩展或者回退的功能

功能实现

一、选取视频并播放

通过 UIImagePickerController 选取视频并且跳转到自定义的编辑控制器

这一部分没什么好说的

示例:


 //选择视频
       @objc func selectVideo() {
           if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
               //初始化图片控制器
               let imagePicker = UIImagePickerController()
               //设置代理
               imagePicker.delegate = self
               //指定图片控制器类型
               imagePicker.sourceType = .photoLibrary
               //只显示视频类型的文件
               imagePicker.mediaTypes = [kUTTypeMovie as String]
               //弹出控制器,显示界面
               self.present(imagePicker, animated: true, completion: nil)
           }
           else {
               print("读取相册错误")
           }
       }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        //获取视频路径(选择后视频会自动复制到app临时文件夹下)
        guard let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL else {
            return
        }
        let pathString = videoURL.relativePath
        print("视频地址:\(pathString)")

        //图片控制器退出
        self.dismiss(animated: true, completion: {
            let editorVC = EditorVideoViewController.init(with: videoURL)

            editorVC.modalPresentationStyle = UIModalPresentationStyle.fullScreen
            self.present(editorVC, animated: true) {

            }
        })
    }

二、按帧获取缩略图初始化视频轨道

CMTime

在讲实现方法之前先介绍一下 CMTime,CMTime 可以用于描述更精确的时间,比如我们想表达视频中的一个瞬间例如 1:01 大多数时候你可以用 NSTimeInterval t = 61.0 这是没有什么大问题的,但浮点数有个比较严重的问题就是无法精确的表达10的-6次方比如将一百万个0.0000001相加,运算结果可能会变成1.0000000000079181,在视频流传输的过程中伴随着大量的数据加减,这样就会造成误差,所以我们需要另一种表达时间的方式,那就是 CMTime

CMTime是一种C函数结构体,有4个成员。

typedef struct {

CMTimeValue value; // 当前的CMTimeValue 的值

CMTimeScale timescale; // 当前的CMTimeValue 的参考标准 (比如:1000)

CMTimeFlags flags;

CMTimeEpoch epoch;

} CMTime;

比如说平时我们所说的如果 timescale = 1000,那么 CMTimeValue = 1000 * 1 = 100

CMTimeScale timescale: 当前的CMTimeValue 的参考标准,它表示1秒的时间被分成了多少份。因为整个CMTime的精度是由它控制的所以它显的尤为重要。例如,当timescale为1的时候,CMTime不能表示1秒一下的时间和1秒内的增长。相同的,当timescale为1000的时候,每秒钟便被分成了1000份,CMTime的value便代表了多少毫秒。

实现方法

调用方法 generateCGImagesAsynchronously(forTimes requestedTimes: [NSValue], completionHandler handler: @escaping AVAssetImageGeneratorCompletionHandler)


 
    open func generateCGImagesAsynchronously(forTimes requestedTimes: [NSValue], completionHandler handler: @escaping AVAssetImageGeneratorCompletionHandler)

浏览官方的注释,可以看出需要传入两个参数 :

requestedTimes: [NSValue]:请求时间的数组(类型为 NSValue)每一个元素包含一个 CMTime,用于指定请求视频的时间。

completionHandler handler: @escaping AVAssetImageGeneratorCompletionHandler: 图像请求完成时将调用的块,由于方法是异步调用的,所以需要返回主线程更新 UI。

示例:


func splitVideoFileUrlFps(splitFileUrl:URL, fps:Float, splitCompleteClosure:@escaping (Bool, [UIImage]) -> Void) {
        var splitImages = [UIImage]()
		
		//初始化 Asset
        let optDict = NSDictionary(object: NSNumber(value: false), forKey: AVURLAssetPreferPreciseDurationAndTimingKey as NSCopying)
        let urlAsset = AVURLAsset(url: splitFileUrl, options: optDict as? [String : Any])

        let cmTime = urlAsset.duration
        let durationSeconds: Float64 = CMTimeGetSeconds(cmTime)

        var times = [NSValue]()
        let totalFrames: Float64 = durationSeconds * Float64(fps)
        var timeFrame: CMTime

		//定义 CMTime 即请求缩略图的时间间隔
        for i in 0...Int(totalFrames) {
            timeFrame = CMTimeMake(value: Int64(i), timescale: Int32(fps))
            let timeValue = NSValue(time: timeFrame)

            times.append(timeValue)
        }

        let imageGenerator = AVAssetImageGenerator(asset: urlAsset)
        imageGenerator.requestedTimeToleranceBefore = CMTime.zero
        imageGenerator.requestedTimeToleranceAfter = CMTime.zero

        let timesCount = times.count
		
		//调用获取缩略图的方法
        imageGenerator.generateCGImagesAsynchronously(forTimes: times) { (requestedTime, image, actualTime, result, error) in

        var isSuccess = false
        switch (result) {
        case AVAssetImageGenerator.Result.cancelled:
            print("cancelled------")

        case AVAssetImageGenerator.Result.failed:
            print("failed++++++")

        case AVAssetImageGenerator.Result.succeeded:

            let framImg = UIImage(cgImage: image!)

            splitImages.append(self.flipImage(image: framImg, orientaion: 1))
            if (Int(requestedTime.value) == (timesCount-1)) { //最后一帧时 回调赋值
                isSuccess = true
                splitCompleteClosure(isSuccess, splitImages)
                print("completed")
            }
        }
        }
    }

				//调用时利用回调更新 UI
self.splitVideoFileUrlFps(splitFileUrl: url, fps: 1) { [weak self](isSuccess, splitImgs) in
            if isSuccess {
                //由于方法是异步的,所以需要回主线程更新 UI
                DispatchQueue.main.async {
                
                    }
                print("图片总数目imgcount:\(String(describing: self?.imageArr.count))")
            }
        }

三、视频指定时间跳转


 
    open func seek(to time: CMTime, toleranceBefore: CMTime, toleranceAfter: CMTime)

三个传入的参数 time: CMTime, toleranceBefore: CMTime, tolearnceAfter: CMTime ,time 参数很好理解,即为想要跳转的时间。那么后面两个参数,按照官方的注释理解,简单来说为“误差的容忍度”,他将会在你拟定的这个区间内跳转,即为 [time-toleranceBefore, time+toleranceAfter] ,当然如果你传 kCMTimeZero(在我当前的版本这个参数被被改为了 CMTime.zero),即为精确搜索,但是这会导致额外的解码时间。

示例:


	let totalTime = self.avPlayer.currentItem?.duration
	let scale = self.avPlayer.currentItem?.duration.timescale
	
	//width:跳转到的视频轨长度 videoWidth:视频轨总长度
 	let process = width / videoWidth
 	
      //快进函数
 	self.avPlayer.seek(to: CMTimeMake(value: Int64(totalTime * process * scale!), timescale: scale!), toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero)

四、播放器监听

通过播放器的监听我们可以改变控制轨道的移动,达到视频播放器和视频轨道的联动



    open func addPeriodicTimeObserver(forInterval interval: CMTime, queue: DispatchQueue?, using block: @escaping (CMTime) -> Void) -> Any

比较重要的一个参数是 interval: CMTime 这决定了代码回调的间隔时间,同时如果你在这个回调里改变视频轨道的 frame 那么这也会决定视频轨道移动的流畅度

示例:


//player的监听
        self.avPlayer.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 120), queue: DispatchQueue.main) { [weak self](time) in
                //与轨道的联动操作
        }

与快进方法冲突的问题

这个监听方法和第三点中的快进方法会造成一个问题:当你拖动视频轨道并且去快进的时候也会触发这个回调于是就造成了 拖动视频轨道 frame (改变 frame) -> 快进方法 -> 触发回调 -> 改变 frame 这一个死循环。那么就得添加判断条件来不去触发这个回调。

快进方法与播放器联动带来的问题

播放视频是异步的,并且快进方法解码视频需要时间,所以就导致了在双方联动的过程中带来的时间差。并且当你认为视频已经快进完成的时候,想要去改变视频轨道的位置,由于解码带来的时间,导致了在回调的时候会传入几个错误的时间,使得视频轨道来回晃动。所以当前项目的做法是,回调时需要判断将要改变的 frame 是否合法(是否过大、过小)

ps:如果关于这两个问题有更好的解决办法,欢迎一起讨论!

五、导出视频


 
    open func insertTimeRange(_ timeRange: CMTimeRange, of track: AVAssetTrack, at startTime: CMTime) throws

 传入的三个参数:

timeRange: CMTimeRange:指定要插入的视频的时间范围

track: AVAssetTrack:指定要插入的视频轨道。仅支持AVURLAssets和AVCompositions的AvassetTrack(从MacOS X 10.10和iOS 8.0开始的AVCompositions)。

starTime: CMTime: 指定合成视频插入的时间点。可以传递kCMTimeInvalid 参数,以指定视频应附加到前一个视频的末尾。

示例:


	let composition = AVMutableComposition()
            //合并视频、音频轨道
    let videoTrack = composition.addMutableTrack(
                        withMediaType: AVMediaType.video, preferredTrackID: CMPersistentTrackID())
  	let audioTrack = composition.addMutableTrack(
                        withMediaType: AVMediaType.audio, preferredTrackID: CMPersistentTrackID())


 	let asset = AVAsset.init(url: self.url)

	var insertTime: CMTime = CMTime.zero

 	let timeScale = self.avPlayer.currentItem?.duration.timescale

			//循环每个片段的信息
	for clipsInfo in self.clipsInfoArr {
            
            //片段的总时间
		let clipsDuration = Double(Float(clipsInfo.width) / self.videoWidth) * self.totalTime
            
            //片段的开始时间
		let startDuration = -Float(clipsInfo.offset) / self.perSecondLength

      	do {
           	try videoTrack?.insertTimeRange(CMTimeRangeMake(start: CMTimeMake(value: Int64(startDuration * Float(timeScale!)), timescale: timeScale!), duration:CMTimeMake(value: Int64(clipsDuration * Double(timeScale!)), timescale: timeScale!)), of: asset.tracks(withMediaType: AVMediaType.video)[0], at: insertTime)
                } catch _ {}

		do {
         	try audioTrack?.insertTimeRange(CMTimeRangeMake(start: CMTimeMake(value: Int64(startDuration * Float(timeScale!)), timescale: timeScale!), duration:CMTimeMake(value: Int64(clipsDuration * Double(timeScale!)), timescale: timeScale!)), of: asset.tracks(withMediaType: AVMediaType.audio)[0], at: insertTime)
      		 } catch _ {}
   		insertTime = CMTimeAdd(insertTime, CMTimeMake(value: Int64(clipsDuration * Double(timeScale!)), timescale: timeScale!))
            }

  		videoTrack?.preferredTransform = CGAffineTransform(rotationAngle: CGFloat.pi / 2)

            //获取合并后的视频路径
  		let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory,.userDomainMask,true)[0]

  		let destinationPath = documentsPath + "/mergeVideo-\(arc4random()%1000).mov"
     	print("合并后的视频:\(destinationPath)")

end:通过这几个 API 再加上交互的逻辑就能实现完整的剪辑功能啦!如果文中有不足的地方,欢迎指出!

到此这篇关于iOS基于AVFoundation 制作用于剪辑视频项目的文章就介绍到这了,更多相关iOS AVFoundation 剪辑视频内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

iOS基于AVFoundation 制作用于剪辑视频项目

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

iOS基于AVFoundation 制作用于剪辑视频项目

目录项目效果图功能实现一、选取视频并播放二、按帧获取缩略图初始化视频轨道三、视频指定时间跳转四、播放器监听五、导出视频最近做了一个剪辑视频的小项目,踩了一些小坑,但还是有惊无险的实现了功能。 其实 Apple 官方也给了一个 UIVideo
2022-05-29

iOS基于AVFoundation怎样制作用于剪辑视频项目

iOS基于AVFoundation怎样制作用于剪辑视频项目,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。最近做了一个剪辑视频的小项目,踩了一些小坑,但还是有惊无
2023-06-21

如何用手机制作属于自己的精美相册MV?学会这些,让你清爽剪辑视频

随着现在科技的发展,各大厂商的手机基本没有千万像素级别的都不好意思宣传,像国产手机四巨头之一的OPPO手机,就是凭借强大的拍照功能而坐拥一大批粉丝。可是有了好看的照片,那么如何剪辑这些照片成了我们应该思考的问题。
2023-06-05

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录