当前位置: 首页>移动开发>正文

AVFoundation框架解析(二十五) —— 播放、录制和合并视频简单示例(一)

版本记录

版本号 时间
V1.0 2020.08.13 星期四

前言

AVFoundation框架是ios中很重要的框架,所有与视频音频相关的软硬件控制都在这个框架里面,接下来这几篇就主要对这个框架进行介绍和讲解。感兴趣的可以看我上几篇。
1. AVFoundation框架解析(一)—— 基本概览
2. AVFoundation框架解析(二)—— 实现视频预览录制保存到相册
3. AVFoundation框架解析(三)—— 几个关键问题之关于框架的深度概括
4. AVFoundation框架解析(四)—— 几个关键问题之AVFoundation探索(一)
5. AVFoundation框架解析(五)—— 几个关键问题之AVFoundation探索(二)
6. AVFoundation框架解析(六)—— 视频音频的合成(一)
7. AVFoundation框架解析(七)—— 视频组合和音频混合调试
8. AVFoundation框架解析(八)—— 优化用户的播放体验
9. AVFoundation框架解析(九)—— AVFoundation的变化(一)
10. AVFoundation框架解析(十)—— AVFoundation的变化(二)
11. AVFoundation框架解析(十一)—— AVFoundation的变化(三)
12. AVFoundation框架解析(十二)—— AVFoundation的变化(四)
13. AVFoundation框架解析(十三)—— 构建基本播放应用程序
14. AVFoundation框架解析(十四)—— VAssetWriter和AVAssetReader的Timecode支持(一)
15. AVFoundation框架解析(十五)—— VAssetWriter和AVAssetReader的Timecode支持(二)
16. AVFoundation框架解析(十六)—— 一个简单示例之播放、录制以及混合视频(一)
17. AVFoundation框架解析(十七)—— 一个简单示例之播放、录制以及混合视频之源码及效果展示(二)
18. AVFoundation框架解析(十八)—— AVAudioEngine之基本概览(一)
19. AVFoundation框架解析(十九)—— AVAudioEngine之详细说明和一个简单示例(二)
20. AVFoundation框架解析(二十)—— AVAudioEngine之详细说明和一个简单示例源码(三)
21. AVFoundation框架解析(二十一)—— 一个简单的视频流预览和播放示例之解析(一)
22. AVFoundation框架解析(二十二)—— 一个简单的视频流预览和播放示例之源码(二)
23. AVFoundation框架解析(二十三) —— 向视频层添加叠加层和动画(一)
24. AVFoundation框架解析(二十四) —— 向视频层添加叠加层和动画(二)

开始

首先看下主要内容:

在本教程中,了解在带有AV FoundationiOS上使用视频的基础知识。 您将播放,录制甚至进行一些简短的视频编辑。内容来自翻译。

接着看一下写作环境

Swift 5, iOS 13, Xcode 11

下面就是正文了。

以编程方式录制视频并播放是您可以用手机完成的最酷的事情之一。但是,几乎没有足够的应用程序提供此功能,您可以使用AV Foundation框架轻松添加。

2010OS X Lion(10.7)iOS 4起,AV Foundation就已成为macOSiOS的一部分。自那时以来,它的发展相当可观,迄今为止已有100多个类。

本教程通过介绍媒体播放和一些轻量编辑,使您开始使用AV Foundation。特别是,您将学习如何:

  • 从媒体库中选择并播放视频。
  • 录制视频并将其保存到媒体库。
  • 将多个剪辑合并为一个具有自定义soundtrack的完整视频。

避免在模拟器上运行本教程中的代码,因为您将无法采集视频。另外,您需要找出一种将视频手动添加到媒体库的方法。换句话说,您确实需要在设备上测试此代码!

为此,您需要是注册的Apple开发人员registered Apple developer。免费帐户可以在本教程中正常工作。

下面就开始啦!

打开入门项目,简单看一下。 该项目包含一个storyboard和几个带有UI的视图控制器,用于简单的视频播放和录制应用程序。

主屏幕包含下面的三个按钮,这些按钮可用于segue到其他视图控制器:

  • 选择并播放视频
  • 录制并保存视频
  • 合并视频

构建并运行和测试按钮。 初始场景中只有三个按钮可以执行任何操作,但是您很快就会对其进行更改!

AVFoundation框架解析(二十五) —— 播放、录制和合并视频简单示例(一),第1张

Selecting and Playing Video

主屏幕上的Select and Play Video按钮可以连接到PlayVideoController。 在本教程的这一部分中,您将添加代码以选择一个视频文件并进行播放。

首先打开PlayVideoViewController.swift,然后在文件顶部添加以下import语句:

import AVKit
import MobileCoreServices

导入AVKit可让您访问AVPlayer,用以播放选定的视频。 MobileCoreServices包含预定义的常量,例如kUTTypeMovie,稍后将需要它们。

接下来,在文件末尾添加以下类扩展名。 确保将它们添加到文件的最底部,类声明的花括号之外:

// MARK: - UIImagePickerControllerDelegate
extension PlayVideoViewController: UIImagePickerControllerDelegate {
}

// MARK: - UINavigationControllerDelegate
extension PlayVideoViewController: UINavigationControllerDelegate {
}

这些扩展将PlayVideoViewController设置为遵循UIImagePickerControllerDelegateUINavigationControllerDelegate协议。

您将使用系统提供的UIImagePickerController来让用户浏览照片库中的视频。 该类通过这些委托协议传达回您的应用程序。 尽管该班级被称为image picker,但请放心,它也可以用于视频!

接下来,回到PlayVideoViewController的主类定义,然后将以下代码添加到playVideo(_ :)

VideoHelper.startMediaBrowser(delegate: self, sourceType: .savedPhotosAlbum)

这是对VideoHelper中名为startMediaBrowser(delegate:sourceType :)helper方法的调用。此调用将打开图像选择器,将委托设置为self.savedPhotosAlbum的源类型选择从相机胶卷中选择图像。稍后,您将在VideoHelper中添加自己的帮助器工具。

要查看此方法的含义,请打开VideoHelper.swift。它执行以下操作:

  • 1) 检查源在设备上是否存在。来源包括相机胶卷,相机本身和完整的照片库。每当您使用UIImagePickerController选择媒体时,此检查都是必不可少的。如果不这样做,则可能会尝试从不存在的来源中选择媒体,这通常会导致崩溃。
  • 2) 如果所需的源可用,它将创建UIImagePickerController并设置其源和媒体类型。由于只想选择视频,因此代码将类型限制为kUTTypeMovie
  • 3) 最后,它以模态形式呈现UIImagePickerController

现在,您准备好为您的项目再一次尝试!构建并运行。在第一个屏幕上点击Select and Play Video,然后在第二个屏幕上点击Play Video。相机胶卷将像这样弹出:

AVFoundation框架解析(二十五) —— 播放、录制和合并视频简单示例(一),第2张

看到视频列表后,选择一个。 您将转到另一个屏幕,其中详细显示了视频以及CancelPlayChoose按钮。 点击Play按钮,毫不奇怪,视频将播放。

但是,如果点击Choose按钮,则该应用程序仅返回到Play Video屏幕! 这是因为您尚未实现任何委托方法来处理从选择器中选择视频。

返回Xcode,再次打开PlayVideoViewController.swift并找到UIImagePickerControllerDelegate扩展。 然后添加以下委托方法实现:

func imagePickerController(
  _ picker: UIImagePickerController,
  didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
) {
  // 1
  guard
    let mediaType = info[UIImagePickerController.InfoKey.mediaType] asString,
    mediaType == (kUTTypeMovie as String),
    let url = info[UIImagePickerController.InfoKey.mediaURL] asURL
    else { return }
  
  // 2
  dismiss(animated: true) {
    //3
    let player = AVPlayer(url: url)
    let vcPlayer = AVPlayerViewController()
    vcPlayer.player = player
    self.present(vcPlayer, animated: true, completion: nil)
  }
}

这是您使用此方法所做的事情:

  • 1) 您可以获取所选媒体和URL的媒体类型,并确保它是视频。
  • 2) 接下来,关闭图像选择器。
  • 3) 在完成块(completion block)中,创建一个AVPlayerViewController来播放媒体。

构建并运行。 点击Select and Play Video,然后点击Play Video,然后从列表中选择一个视频。 该视频将在媒体播放器中播放。

AVFoundation框架解析(二十五) —— 播放、录制和合并视频简单示例(一),第3张

Recording Video

现在您可以播放视频了,现在该使用设备的摄像机录制视频并将其保存到媒体库中了。

打开RecordVideoViewController.swift并添加以下导入:

import MobileCoreServices

然后,将以下内容添加到文件末尾:

// MARK: - UIImagePickerControllerDelegate
extension RecordVideoViewController: UIImagePickerControllerDelegate {
}

// MARK: - UINavigationControllerDelegate
extension RecordVideoViewController: UINavigationControllerDelegate {
}

它采用与PlayVideoViewController相同的协议。

接下来,将以下代码添加到record(_ :)

VideoHelper.startMediaBrowser(delegate: self, sourceType: .camera)

它使用与PlayVideoViewController中相同的helper方法,除了它访问.camera以指示图像选择器以内置照相机模式打开。

构建并运行以查看您到目前为止所拥有的。

转到Record屏幕,然后点击Record Video。 代替相机图库,将打开相机用户界面。 当alert对话框询问摄像机权限和麦克风权限时,单击OK

最后,点击屏幕底部的红色录制按钮开始录制视频; 完成录制后,再次点按它。

AVFoundation框架解析(二十五) —— 播放、录制和合并视频简单示例(一),第4张

现在,您有两个选择:使用录制的视频或重录。 点击Use Video。 您会注意到,它只是关闭了视图控制器。 这是因为-您猜对了-您尚未实现适当的委托方法来将录制的视频保存到媒体库。

Saving Video

返回RecordVideoViewController.swift,将以下方法添加到UIImagePickerControllerDelegate扩展中:

func imagePickerController(
  _ picker: UIImagePickerController,
  didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
) {
  dismiss(animated: true, completion: nil)
  
  guard
    let mediaType = info[UIImagePickerController.InfoKey.mediaType] asString,
    mediaType == (kUTTypeMovie as String),
    // 1
    let url = info[UIImagePickerController.InfoKey.mediaURL] asURL,
    // 2
    UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(url.path)
    else { return }
  
  // 3
  UISaveVideoAtPathToSavedPhotosAlbum(
    url.path,
    self,
    #selector(video(_:didFinishSavingWithError:contextInfo:)),
    nil)
}

不必担心该错误-很快就会解决。

  • 1) 和以前一样,委托方法为您提供指向视频的URL
  • 2) 验证该应用程序可以将文件保存到设备的相册中。
  • 3) 如果可以,请保存。

SDK所提供的UISaveVideoAtPathToSavedPhotosAlbum功能可将视频保存到设备的相册中。 您向其传递要保存的视频的路径以及要回调的目标和操作,这将使您知道保存操作的状态。

接下来,将回调的实现添加到主类定义中:

@objc func video(
  _ videoPath: String,
  didFinishSavingWithError error: Error?,
  contextInfo info: AnyObject
) {
  let title = (error == nil) "Success" : "Error"
  let message = (error == nil) "Video was saved" : "Video failed to save"

  let alert = UIAlertController(
    title: title,
    message: message,
    preferredStyle: .alert)
  alert.addAction(UIAlertAction(
    title: "OK",
    style: UIAlertAction.Style.cancel,
    handler: nil))
  present(alert, animated: true, completion: nil)
}

回调(callback)方法仅向用户显示alert,并根据错误状态宣布是否保存了视频文件。

构建并运行。 录制视频,并在录制完成后选择Use Video。 如果询问您是否有权保存到视频库,请点击OK。 当Video was saved弹窗弹出时,您就成功将视频保存到了照片库!

AVFoundation框架解析(二十五) —— 播放、录制和合并视频简单示例(一),第5张

现在您可以播放视频和录制视频了,该是下一步了,尝试一些简单的视频编辑了。

Merging Videos

该应用程序的最后一项功能是进行一些编辑。您的用户将从音乐库中选择两个视频和一首歌曲,然后该应用将合并两个视频并混入音乐。

该项目已经在MergeVideoViewController.swift中实现了入门实施,其代码类似于您编写的用于播放视频的代码。最大的区别是,合并时,用户必须选择两个视频。该部分已经设置好,因此用户可以进行两个选择,这些选择将存储在firstAssetsecondAsset中。

下一步是添加功能以选择音频文件。

1. Selecting the Audio File

UIImagePickerController提供了从媒体库中仅选择视频和图像的功能。要从音乐库中选择音频文件,必须使用MPMediaPickerController。它的工作原理与UIImagePickerController相同,但是它访问图像库中的音频文件,而不是图像和视频。

打开MergeVideoViewController.swift并将以下代码添加到loadAudio(_ :)

let mediaPickerController = MPMediaPickerController(mediaTypes: .any)
mediaPickerController.delegate = self
mediaPickerController.prompt = "Select Audio"
present(mediaPickerController, animated: true, completion: nil)

上面的代码创建一个新的MPMediaPickerController实例,并将其显示为模式视图控制器。

构建并运行。 现在,点击Merge Video,然后点击Load Audio以访问设备上的音频库。

当然,您的设备上需要一些音频文件。 否则,列表将为空。 这些歌曲还必须实际显示在设备上,因此请确保您不尝试从云中加载歌曲。

AVFoundation框架解析(二十五) —— 播放、录制和合并视频简单示例(一),第6张

从列表中选择一首歌曲,您会发现没有任何反应。 那就对了! MPMediaPickerController需要委托方法!

要实现它们,请在文件底部找到MPMediaPickerControllerDelegate扩展,然后向其中添加以下两个方法:

func mediaPicker(
  _ mediaPicker: MPMediaPickerController,
  didPickMediaItems mediaItemCollection: MPMediaItemCollection
) {
  // 1
  dismiss(animated: true) {
    // 2
    let selectedSongs = mediaItemCollection.items
    guard let song = selectedSongs.first else { return }
    
    // 3
    let title: String
    let message: String
    if let url = song.value(forProperty: MPMediaItemPropertyAssetURL) asURL {
      self.audioAsset = AVAsset(url: url)
      title = "Asset Loaded"
      message = "Audio Loaded"
    } else {
      self.audioAsset = nil
      title = "Asset Not Available"
      message = "Audio Not Loaded"
    }

    // 4
    let alert = UIAlertController(
      title: title,
      message: message,
      preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
    self.present(alert, animated: true, completion: nil)
  }
}

func mediaPickerDidCancel(_ mediaPicker: MPMediaPickerController) {
  // 5
  dismiss(animated: true, completion: nil)
}

上面的代码类似于UIImagePickerController的委托方法。 它的作用是:

  • 1) 像以前一样dismiss选择器。
  • 2) 找到选定的歌曲,然后从中找到第一首(如果选择了多首)。
  • 3) 获取指向支持歌曲的媒体资源的URL。 然后使AVAsset指向所选的歌曲。
  • 4) 最后,对于mediaPicker(_:didPickMediaItems :),显示alert以指示资源是否已成功加载。
  • 5) 在取消媒体选择器的情况下,只需关闭视图控制器即可。

构建并运行,然后转到Merge Videos屏幕。 选择一个音频文件,您将看到Audio Loaded消息。

AVFoundation框架解析(二十五) —— 播放、录制和合并视频简单示例(一),第7张

现在,您的所有资源都已正确加载,是时候将各种媒体文件合并为一个文件了。 但是在进入该代码之前,您需要进行一些设置。

2. Merging Completion Handler

您将很快编写代码以合并您的资产。 这将需要一个完成处理程序,以将最终视频保存到相册中。 您将首先添加它。

MergeVideoViewController.swift文件的顶部添加以下import语句:

import Photos

然后,将以下方法添加到MergeVideoViewController

func exportDidFinish(_ session: AVAssetExportSession) {
  // 1
  activityMonitor.stopAnimating()
  firstAsset = nil
  secondAsset = nil
  audioAsset = nil

  // 2
  guard
    session.status == AVAssetExportSession.Status.completed,
    let outputURL = session.outputURL
    else { return }

  // 3
  let saveVideoToPhotos = {
    // 4
    let changes: () -> Void = {
      PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL)
    }
    PHPhotoLibrary.shared().performChanges(changes) { saved, error in
      DispatchQueue.main.async {
        let success = saved && (error == nil)
        let title = success "Success" : "Error"
        let message = success "Video saved" : "Failed to save video"

        let alert = UIAlertController(
          title: title,
          message: message,
          preferredStyle: .alert)
        alert.addAction(UIAlertAction(
          title: "OK",
          style: UIAlertAction.Style.cancel,
          handler: nil))
        self.present(alert, animated: true, completion: nil)
      }
    }
  }
    
  // 5
  if PHPhotoLibrary.authorizationStatus() != .authorized {
    PHPhotoLibrary.requestAuthorization { status in
      if status == .authorized {
        saveVideoToPhotos()
      }
    }
  } else {
    saveVideoToPhotos()
  }
}

该代码的作用如下:

  • 1) 有一个spinner,用于在处理资产时进行动画处理。 这将停止spinner,然后清除资源并准备选择新资源。
  • 2) 确保处理完成,并且有结果视频的URL
  • 3) 创建一个闭包
  • 4) 告诉照片库从显示的视频发出create request,然后显示alert以表明成功或失败。
  • 5) 检查是否有访问照片库的权限。 如果没有权限,则在运行保存视频的闭包之前要求它。 否则,只要已授予许可,就立即运行闭包。

现在,您将添加一些代码到merge(_:)。 因为有很多代码,所以您将分步完成。

Merging: Step 1

在此步骤中,您将视频合并为一个长视频。

将以下代码添加到merge(_ :)

guard
  let firstAsset = firstAsset,
  let secondAsset = secondAsset
  else { return }

activityMonitor.startAnimating()

// 1
let mixComposition = AVMutableComposition()

// 2
guard
  let firstTrack = mixComposition.addMutableTrack(
    withMediaType: .video,
    preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
  else { return }
    
// 3
do {
  try firstTrack.insertTimeRange(
    CMTimeRangeMake(start: .zero, duration: firstAsset.duration),
    of: firstAsset.tracks(withMediaType: .video)[0],
    at: .zero)
} catch {
  print("Failed to load first track")
  return
}

// 4
guard
  let secondTrack = mixComposition.addMutableTrack(
    withMediaType: .video,
    preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
  else { return }
    
do {
  try secondTrack.insertTimeRange(
    CMTimeRangeMake(start: .zero, duration: secondAsset.duration),
    of: secondAsset.tracks(withMediaType: .video)[0],
    at: firstAsset.duration)
} catch {
  print("Failed to load second track")
  return
}

// 5
// TODO: PASTE CODE A

在上面的代码中:

  • 1) 您创建一个AVMutableComposition来保存您的视频和音频轨道。
  • 2) 接下来,为视频创建一个AVMutableCompositionTrack并将其添加到您的AVMutableComposition中。
  • 3) 然后,将视频从第一个视频资产插入到此轨道。

请注意,insertTimeRange(_:ofTrack:atStartTime :)允许您将视频的一部分而不是整个部分插入到您的主要合成作品中。这样,您可以将视频修剪到您选择的时间范围。

在这种情况下,您要插入整个视频,因此创建一个从CMTime.zero到视频资源持续时间的时间范围。

  • 4) 接下来,您对第二个视频资产执行相同的操作。

请注意,代码是如何在时间.zero插入firstAsset的,然后在第一个视频的末尾插入secondAsset的。这是因为本教程假定您要一个接一个地使用视频资源,但是您也可以通过播放时间范围来重叠资产。

  • 5) // TODO: PASTE CODE A是一个标记-您将在下一部分中用代码替换此行。

在此步骤中,您将设置两个单独的AVMutableCompositionTrack实例。现在,您需要将AVMutableVideoCompositionLayerInstruction应用于每个轨道,以便进行一些编辑。

Merging the Videos: Step 2

接下来是将instructions添加到composition中,以告诉您如何合并资源。

merge(_ :)中的上面的跟踪代码之后添加下一部分代码。将// TODO:PASTE CODE A替换为以下代码:

// 6
let mainInstruction = AVMutableVideoCompositionInstruction()
mainInstruction.timeRange = CMTimeRangeMake(
  start: .zero,
  duration: CMTimeAdd(firstAsset.duration, secondAsset.duration))

// 7
let firstInstruction = AVMutableVideoCompositionLayerInstruction(
  assetTrack: firstTrack)
firstInstruction.setOpacity(0.0, at: firstAsset.duration)
let secondInstruction = AVMutableVideoCompositionLayerInstruction(
  assetTrack: secondTrack)

// 8
mainInstruction.layerInstructions = [firstInstruction, secondInstruction]
let mainComposition = AVMutableVideoComposition()
mainComposition.instructions = [mainInstruction]
mainComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
mainComposition.renderSize = CGSize(
  width: UIScreen.main.bounds.width,
  height: UIScreen.main.bounds.height)

// 9                          
// TODO: PASTE CODE B

这是这段代码中发生的事情:

  • 6) 首先,设置mainInstruction来包装整个instructions。请注意,此处的总时间是第一个资源的总长与第二个资源的总长之和。
  • 7) 接下来,您设置了两个instructions,每个资源对应一个instructions。第一个视频的instructions需要额外添加:您将其不透明度最后设置为0,以便在第二个视频开始时不可见。
  • 8) 现在,您已经有了第一条和第二条轨道的AVMutableVideoCompositionLayerInstruction实例,您只需将它们添加到mainInstruction中。接下来,将mainInstruction添加到AVMutableVideoComposition实例的指令属性。您还可以将合成的帧频设置为30帧/秒。
  • 9) // TODO: PASTE CODE B是一个标记-您将在下一部分中用代码替换此行。

好的,现在您已经合并了两个视频文件。是时候为他们添加一些声音了!

Merging the Audio: Step 3

要使片段具有音乐风格,请将以下代码添加到merge(_ :)。将// TODO:PASTE CODE B替换为以下代码:

// 10
if let loadedAudioAsset = audioAsset {
  let audioTrack = mixComposition.addMutableTrack(
    withMediaType: .audio,
    preferredTrackID: 0)
  do {
    try audioTrack?.insertTimeRange(
      CMTimeRangeMake(
        start: .zero,
        duration: CMTimeAdd(
          firstAsset.duration,
          secondAsset.duration)),
      of: loadedAudioAsset.tracks(withMediaType: .audio)[0],
      at: .zero)
  } catch {
    print("Failed to load Audio track")
  }
}

// 11
guard
  let documentDirectory = FileManager.default.urls(
    for: .documentDirectory,
    in: .userDomainMask).first
  else { return }
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .short
let date = dateFormatter.string(from: Date())
let url = documentDirectory.appendingPathComponent("mergeVideo-\(date).mov")

// 12
guard let exporter = AVAssetExportSession(
  asset: mixComposition,
  presetName: AVAssetExportPresetHighestQuality)
  else { return }
exporter.outputURL = url
exporter.outputFileType = AVFileType.mov
exporter.shouldOptimizeForNetworkUse = true
exporter.videoComposition = mainComposition

// 13
exporter.exportAsynchronously {
  DispatchQueue.main.async {
    self.exportDidFinish(exporter)
  }
}

上面的代码是这样的:

  • 10) 与视频轨道相似,您可以为音频创建一个新轨道并将其添加到主要composition中。您将音频时间范围设置为第一和第二视频的时长之和,因为这将是视频的完整长度。
  • 11) 在保存最终视频之前,需要一个保存文件的路径。根据当前日期和时间创建一个唯一的文件名,该文件名指向documents文件夹中的文件。
  • 12) 渲染并导出合并的视频。为此,您将创建一个AVAssetExportSession,它将对合成的内容进行转码以创建由指定的导出预设描述的格式输出。由于您已经配置了AVMutableVideoComposition,因此您只需将其分配给导出器即可。
  • 13) 在使用包含源媒体,导出presetNameoutputFileType的资源初始化导出会话后,可以通过调用exportAsynchronously()运行导出。

因为代码异步执行导出,所以此方法立即返回。无论导出失败,完成还是用户取消,代码都会调用您提供给exportAsynchronously()的完成处理程序。

完成后,导出的状态status属性指示导出是否成功完成。如果失败,则导出器的error属性的值将提供有关失败原因的其他信息。

AVComposition组合了来自多个基于文件的来源的媒体数据。在最高层,AVComposition是轨道的集合,每个轨道都呈现特定类型的媒体,例如音频或视频。 AVCompositionTrack的实例表示单个轨道。

同样,AVMutableCompositionAVMutableCompositionTrack也提供了用于构建合成的更高级别的接口。这些对象提供了您之前见过的插入,移除和缩放操作,这些操作将再次出现。

最后,构建并运行。

选择两个视频和一个音频文件,然后合并选定的文件。您会看到一条Video Saved消息,表明合成成功。此时,您的新视频将出现在相册中。

AVFoundation框架解析(二十五) —— 播放、录制和合并视频简单示例(一),第8张

转到相册或使用应用程序中的Select and Play Video屏幕浏览,您可能会注意到合并视频中的一些方向问题。 竖屏视频可能处于横向模式,有时视频会倒置。

AVFoundation框架解析(二十五) —— 播放、录制和合并视频简单示例(一),第9张

这是由于默认的AVAsset方向。 使用默认的iPhone相机应用程序记录的所有电影和图像文件都将视频帧设置为横向,因此iPhone以横向模式保存媒体。 接下来,您将解决这些问题。

Orienting Video

AVAsset有一个PreferredTransform,其中包含媒体方向信息。 每当您使用Photos应用程序或QuickTime查看媒体文件时,它会将其应用于媒体文件。

在上面的代码中,您尚未对AVAsets进行转换,因此没有定向问题。 幸运的是,这很容易解决。

但是,在执行此操作之前,需要在VideoHelper.swiftVideoHelper中添加以下帮助器方法:

static func orientationFromTransform(
  _ transform: CGAffineTransform
) -> (orientation: UIImage.Orientation, isPortrait: Bool) {
  var assetOrientation = UIImage.Orientation.up
  var isPortrait = false
  let tfA = transform.a
  let tfB = transform.b
  let tfC = transform.c
  let tfD = transform.d

  if tfA == 0 && tfB == 1.0 && tfC == -1.0 && tfD == 0 {
    assetOrientation = .right
    isPortrait = true
  } else if tfA == 0 && tfB == -1.0 && tfC == 1.0 && tfD == 0 {
    assetOrientation = .left
    isPortrait = true
  } else if tfA == 1.0 && tfB == 0 && tfC == 0 && tfD == 1.0 {
    assetOrientation = .up
  } else if tfA == -1.0 && tfB == 0 && tfC == 0 && tfD == -1.0 {
    assetOrientation = .down
  }
  return (assetOrientation, isPortrait)
}

此代码分析仿射变换(affine transform),以确定输入视频的方向。

接下来,添加以下import

import AVFoundation

以及该类的另一个helper方法:

static func videoCompositionInstruction(
  _ track: AVCompositionTrack,
  asset: AVAsset
) -> AVMutableVideoCompositionLayerInstruction {
  // 1
  let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track)

  // 2
  let assetTrack = asset.tracks(withMediaType: AVMediaType.video)[0]

  // 3
  let transform = assetTrack.preferredTransform
  let assetInfo = orientationFromTransform(transform)

  var scaleToFitRatio = UIScreen.main.bounds.width / assetTrack.naturalSize.width
  if assetInfo.isPortrait {
    // 4
    scaleToFitRatio = UIScreen.main.bounds.width / assetTrack.naturalSize.height
    let scaleFactor = CGAffineTransform(
      scaleX: scaleToFitRatio,
      y: scaleToFitRatio)
    instruction.setTransform(
      assetTrack.preferredTransform.concatenating(scaleFactor),
      at: .zero)
  } else {
    // 5
    let scaleFactor = CGAffineTransform(
      scaleX: scaleToFitRatio,
      y: scaleToFitRatio)
    var concat = assetTrack.preferredTransform.concatenating(scaleFactor)
      .concatenating(CGAffineTransform(
        translationX: 0,
        y: UIScreen.main.bounds.width / 2))
    if assetInfo.orientation == .down {
      let fixUpsideDown = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
      let windowBounds = UIScreen.main.bounds
      let yFix = assetTrack.naturalSize.height + windowBounds.height
      let centerFix = CGAffineTransform(
        translationX: assetTrack.naturalSize.width,
        y: yFix)
      concat = fixUpsideDown.concatenating(centerFix).concatenating(scaleFactor)
    }
    instruction.setTransform(concat, at: .zero)
  }

  return instruction
}

该方法获取一个轨道和一个资源,并返回一个AVMutableVideoCompositionLayerInstruction,其中包装了使视频朝上的必要仿射变换(affine transform)。这是逐步进行的操作:

  • 1) 您创建AVMutableVideoCompositionLayerInstruction并将其与轨道关联。
  • 2) 接下来,从您的AVAsset创建AVAssetTrackAVAssetTrack为所有资源提供跟踪级别的检查接口。您需要此对象来访问资源的尺寸和preferredTransform
  • 3) 然后,保存首选的变换(preferred transform)和使视频适合当前屏幕所需的缩放比例。您将在以下步骤中使用这些值。
  • 4) 如果视频是纵向视频,则需要重新计算比例因子-默认计算是横向视频。然后,您要做的就是应用方向旋转和比例变换。
  • 5) 如果视频是横向播放,则可以采用类似的步骤来应用缩放和变换。但是,有一项额外的检查,因为用户可能会以左横向或右横向制作视频。

由于有两个横向,因此纵横比将匹配,但视频可能会旋转180度。对于.down视频方向的额外检查可以解决这种情况。

设置好helper方法后,在MergeVideoViewController.swift中找到merge(_ :)。找到创建firstInstructionsecondInstruction的位置,并将它们替换为以下内容:

let firstInstruction = VideoHelper.videoCompositionInstruction(
  firstTrack,
  asset: firstAsset)
let secondInstruction = VideoHelper.videoCompositionInstruction(
   secondTrack,
   asset: secondAsset)

上面的更改将使用新的helper函数并实现所需的旋转修复程序。

哇-就是这样!

构建并运行。 通过将两个视频(以及一个音频文件)结合在一起来创建新视频,当您播放视频时,您会发现方向问题消失了。

AVFoundation框架解析(二十五) —— 播放、录制和合并视频简单示例(一),第10张

在播放视频时,AV Foundation为您提供了很大的灵活性。 您还可以应用任何类型的CGAffineTransform合并,缩放或定位视频。

如果您尚未这样做,请看一下AV Foundation上的WWDC videos,例如WWDC 2016 session 503, Advances in AVFoundation Playback

另外,请务必查看Apple AVFoundation Framework documentation。

后记

本篇主要讲述了播放、录制和合并视频简单示例,感兴趣的给个赞或者关注~~~

AVFoundation框架解析(二十五) —— 播放、录制和合并视频简单示例(一),第11张

https://www.xamrdz.com/mobile/4j51993958.html

相关文章: