AVFoundation框架解析(二十五) —— 播放、录制和合

2020-08-13  本文已影响0人  刀客传奇

版本记录

版本号 时间
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。特别是,您将学习如何:

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

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

下面就开始啦!

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

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

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


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。它执行以下操作:

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

看到视频列表后,选择一个。 您将转到另一个屏幕,其中详细显示了视频以及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] as? String,
    mediaType == (kUTTypeMovie as String),
    let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
    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)
  }
}

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

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


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

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

现在,您有两个选择:使用录制的视频或重录。 点击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] as? String,
    mediaType == (kUTTypeMovie as String),
    // 1
    let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL,
    // 2
    UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(url.path)
    else { return }
  
  // 3
  UISaveVideoAtPathToSavedPhotosAlbum(
    url.path,
    self,
    #selector(video(_:didFinishSavingWithError:contextInfo:)),
    nil)
}

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

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弹窗弹出时,您就成功将视频保存到了照片库!

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


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以访问设备上的音频库。

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

从列表中选择一首歌曲,您会发现没有任何反应。 那就对了! 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) as? URL {
      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的委托方法。 它的作用是:

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

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

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()
  }
}

该代码的作用如下:

现在,您将添加一些代码到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

在上面的代码中:

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

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

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

在此步骤中,您将设置两个单独的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

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

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


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)
  }
}

上面的代码是这样的:

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

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

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

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

最后,构建并运行。

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

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

这是由于默认的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)。这是逐步进行的操作:

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

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

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

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

哇-就是这样!

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

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

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

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

后记

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

上一篇下一篇

猜你喜欢

热点阅读