基于 TensorFlow 示例 Swift 录制和播放视频

Posted

技术标签:

【中文标题】基于 TensorFlow 示例 Swift 录制和播放视频【英文标题】:Record and play Video based on TensorFlow example Swift 【发布时间】:2021-07-01 20:09:23 【问题描述】:

#DEFINE UPDATE

我意识到我忘记请求录制许可了。现在已经解决了。但是,当我按下“录制按钮”时,我收到错误 Cannot create file。所以当我开始录制时,路径可能有问题?

#UNDEF 更新

我正在开发一个应用程序,我希望在该应用程序中拥有自己的神经网络,并具有开始录制视频的功能。此后我想播放视频并使用来自神经网络的信息。

我在 android 中有一个工作功能,现在我正在尝试为 iPhone 制作类似的东西。作为开始,我使用了来自TensorFlowLiteImageClassifierExample。第一个任务是添加一个按钮Record 开始录制视频,然后添加一个按钮Play 播放视频。

我已经实现了这两个功能,但是当我尝试播放视频时,它只是在加载。可能是录制不工作,或者视频播放器不工作(或两者兼而有之)。我已经检查过了,所以路径是一样的。

我对 ios 开发不是很熟悉,所以可以提供一些帮助。

这是我开始的base。

这是我略微采纳的ViewController

// Copyright 2019 The TensorFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import AVFoundation
import AVKit
import UIKit

class ViewController: UIViewController 
    
    // MARK: Storyboards Connections
    @IBOutlet weak var previewView: PreviewView!
    @IBOutlet weak var cameraUnavailableLabel: UILabel!
    @IBOutlet weak var resumeButton: UIButton!
    @IBOutlet weak var bottomSheetView: CurvedView!
    
    @IBOutlet weak var bottomSheetViewBottomSpace: NSLayoutConstraint!
    @IBOutlet weak var bottomSheetStateImageView: UIImageView!
    // MARK: Constants
    private let animationDuration = 0.5
    private let collapseTransitionThreshold: CGFloat = -40.0
    private let expandThransitionThreshold: CGFloat = 40.0
    private let delayBetweenInferencesMs: Double = 1000
    
    // MARK: Instance Variables
    // Holds the results at any time
    private var result: Result?
    private var initialBottomSpace: CGFloat = 0.0
    private var previousInferenceTimeMs: TimeInterval = Date.distantPast.timeIntervalSince1970 * 1000
    
    // MARK: Controllers that manage functionality
    // Handles all the camera related functionality
    private lazy var cameraCapture = CameraFeedManager(previewView: previewView)
    
    private var isRecording = false // <<<----- Mine
    private let captureSession: AVCaptureSession = AVCaptureSession()
    
    // Handles all data preprocessing and makes calls to run inference through the `Interpreter`.
    private var modelDataHandler: ModelDataHandler? =
        ModelDataHandler(modelFileInfo: MobileNet.modelInfo, labelsFileInfo: MobileNet.labelsInfo)

    @IBAction func startRecording(_ sender: Any) . // <<<----- Mine
        print("Recording pressed")
        if (!isRecording) 
            cameraCapture.startRecording()
         else 
            cameraCapture.stopRecording()
        
        isRecording = !isRecording
    
    // Handles the presenting of results on the screen
    private var inferenceViewController: InferenceViewController?
    
    // MARK: View Handling Methods
    override func viewDidLoad() 
        super.viewDidLoad()
        
        guard modelDataHandler != nil else 
            fatalError("Model set up failed")
        
        
        #if targetEnvironment(simulator)
        previewView.shouldUseClipboardImage = true
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(classifyPasteboardImage),
                                               name: UIApplication.didBecomeActiveNotification,
                                               object: nil)
        #endif
        cameraCapture.delegate = self
        
        addPanGesture()
    
    
    override func viewWillAppear(_ animated: Bool) 
        super.viewWillAppear(animated)
        
        changeBottomViewState()
        
        #if !targetEnvironment(simulator)
        cameraCapture.checkCameraConfigurationAndStartSession()
        #endif
    
    
    #if !targetEnvironment(simulator)
    override func viewWillDisappear(_ animated: Bool) 
        super.viewWillDisappear(animated)
        cameraCapture.stopSession()
    
    #endif
    
    override var preferredStatusBarStyle: UIStatusBarStyle 
        return .lightContent
    
    
    func presentUnableToResumeSessionAlert() 
        let alert = UIAlertController(
            title: "Unable to Resume Session",
            message: "There was an error while attempting to resume session.",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        
        self.present(alert, animated: true)
    
    
    // MARK: Storyboard Segue Handlers
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) 
        super.prepare(for: segue, sender: sender)
        
        if segue.identifier == "EMBED" 
            
            guard let tempModelDataHandler = modelDataHandler else 
                return
            
            inferenceViewController = segue.destination as? InferenceViewController
            inferenceViewController?.wantedInputHeight = tempModelDataHandler.inputHeight
            inferenceViewController?.wantedInputWidth = tempModelDataHandler.inputWidth
            inferenceViewController?.maxResults = tempModelDataHandler.resultCount
            inferenceViewController?.threadCountLimit = tempModelDataHandler.threadCountLimit
            inferenceViewController?.delegate = self
            
        
    
    
    @objc func classifyPasteboardImage() 
        guard let image = UIPasteboard.general.images?.first else 
            return
        
        
        guard let buffer = CVImageBuffer.buffer(from: image) else 
            return
        
        
        previewView.image = image
        
        DispatchQueue.global().async 
            self.didOutput(pixelBuffer: buffer)
        
    
    
    deinit 
        NotificationCenter.default.removeObserver(self)
    
    


// MARK: InferenceViewControllerDelegate Methods
extension ViewController: InferenceViewControllerDelegate 
    
    func didChangeThreadCount(to count: Int) 
        if modelDataHandler?.threadCount == count  return 
        modelDataHandler = ModelDataHandler(
            modelFileInfo: MobileNet.modelInfo,
            labelsFileInfo: MobileNet.labelsInfo,
            threadCount: count
        )
    


// MARK: CameraFeedManagerDelegate Methods
extension ViewController: CameraFeedManagerDelegate 
    
    func didOutput(pixelBuffer: CVPixelBuffer) 
        let currentTimeMs = Date().timeIntervalSince1970 * 1000
        guard (currentTimeMs - previousInferenceTimeMs) >= delayBetweenInferencesMs else  return 
        previousInferenceTimeMs = currentTimeMs
        
        // Pass the pixel buffer to TensorFlow Lite to perform inference.
        result = modelDataHandler?.runModel(onFrame: pixelBuffer)
        
        // Display results by handing off to the InferenceViewController.
        DispatchQueue.main.async 
            let resolution = CGSize(width: CVPixelBufferGetWidth(pixelBuffer), height: CVPixelBufferGetHeight(pixelBuffer))
            self.inferenceViewController?.inferenceResult = self.result
            self.inferenceViewController?.resolution = resolution
            self.inferenceViewController?.tableView.reloadData()
        
    
    
    // MARK: Session Handling Alerts
    func sessionWasInterrupted(canResumeManually resumeManually: Bool) 
        
        // Updates the UI when session is interupted.
        if resumeManually 
            self.resumeButton.isHidden = false
         else 
            self.cameraUnavailableLabel.isHidden = false
        
    
    
    func sessionInterruptionEnded() 
        // Updates UI once session interruption has ended.
        if !self.cameraUnavailableLabel.isHidden 
            self.cameraUnavailableLabel.isHidden = true
        
        
        if !self.resumeButton.isHidden 
            self.resumeButton.isHidden = false
        
    
    
    func sessionRunTimeErrorOccured() 
        // Handles session run time error by updating the UI and providing a button if session can be manually resumed.
        self.resumeButton.isHidden = false
        previewView.shouldUseClipboardImage = true
    
    
    func presentCameraPermissionsDeniedAlert() 
        let alertController = UIAlertController(title: "Camera Permissions Denied", message: "Camera permissions have been denied for this app. You can change this by going to Settings", preferredStyle: .alert)
        
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
        let settingsAction = UIAlertAction(title: "Settings", style: .default)  (action) in
            UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
        
        alertController.addAction(cancelAction)
        alertController.addAction(settingsAction)
        
        present(alertController, animated: true, completion: nil)
        
        previewView.shouldUseClipboardImage = true
    
    
    func presentVideoConfigurationErrorAlert() 
        let alert = UIAlertController(title: "Camera Configuration Failed", message: "There was an error while configuring camera.", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        
        self.present(alert, animated: true)
        previewView.shouldUseClipboardImage = true
    


// MARK: Bottom Sheet Interaction Methods
extension ViewController 
    
    // MARK: Bottom Sheet Interaction Methods
    /**
     This method adds a pan gesture to make the bottom sheet interactive.
     */
    private func addPanGesture() 
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(ViewController.didPan(panGesture:)))
        bottomSheetView.addGestureRecognizer(panGesture)
    
    
    
    /** Change whether bottom sheet should be in expanded or collapsed state.
     */
    private func changeBottomViewState() 
        
        guard let inferenceVC = inferenceViewController else 
            return
        
        
        if bottomSheetViewBottomSpace.constant == inferenceVC.collapsedHeight - bottomSheetView.bounds.size.height 
            
            bottomSheetViewBottomSpace.constant = 0.0
        
        else 
            bottomSheetViewBottomSpace.constant = inferenceVC.collapsedHeight - bottomSheetView.bounds.size.height
        
        setImageBasedOnBottomViewState()
    
    
    /**
     Set image of the bottom sheet icon based on whether it is expanded or collapsed
     */
    private func setImageBasedOnBottomViewState() 
        
        if bottomSheetViewBottomSpace.constant == 0.0 
            bottomSheetStateImageView.image = UIImage(named: "down_icon")
        
        else 
            bottomSheetStateImageView.image = UIImage(named: "up_icon")
        
    
    
    /**
     This method responds to the user panning on the bottom sheet.
     */
    @objc func didPan(panGesture: UIPanGestureRecognizer) 
        
        // Opens or closes the bottom sheet based on the user's interaction with the bottom sheet.
        let translation = panGesture.translation(in: view)
        
        switch panGesture.state 
        case .began:
            initialBottomSpace = bottomSheetViewBottomSpace.constant
            translateBottomSheet(withVerticalTranslation: translation.y)
        case .changed:
            translateBottomSheet(withVerticalTranslation: translation.y)
        case .cancelled:
            setBottomSheetLayout(withBottomSpace: initialBottomSpace)
        case .ended:
            translateBottomSheetAtEndOfPan(withVerticalTranslation: translation.y)
            setImageBasedOnBottomViewState()
            initialBottomSpace = 0.0
        default:
            break
        
    
    
    /**
     This method sets bottom sheet translation while pan gesture state is continuously changing.
     */
    private func translateBottomSheet(withVerticalTranslation verticalTranslation: CGFloat) 
        
        let bottomSpace = initialBottomSpace - verticalTranslation
        guard bottomSpace <= 0.0 && bottomSpace >= inferenceViewController!.collapsedHeight - bottomSheetView.bounds.size.height else 
            return
        
        setBottomSheetLayout(withBottomSpace: bottomSpace)
    
    
    /**
     This method changes bottom sheet state to either fully expanded or closed at the end of pan.
     */
    private func translateBottomSheetAtEndOfPan(withVerticalTranslation verticalTranslation: CGFloat) 
        
        // Changes bottom sheet state to either fully open or closed at the end of pan.
        let bottomSpace = bottomSpaceAtEndOfPan(withVerticalTranslation: verticalTranslation)
        setBottomSheetLayout(withBottomSpace: bottomSpace)
    
    
    /**
     Return the final state of the bottom sheet view (whether fully collapsed or expanded) that is to be retained.
     */
    private func bottomSpaceAtEndOfPan(withVerticalTranslation verticalTranslation: CGFloat) -> CGFloat 
        
        // Calculates whether to fully expand or collapse bottom sheet when pan gesture ends.
        var bottomSpace = initialBottomSpace - verticalTranslation
        
        var height: CGFloat = 0.0
        if initialBottomSpace == 0.0 
            height = bottomSheetView.bounds.size.height
        
        else 
            height = inferenceViewController!.collapsedHeight
        
        
        let currentHeight = bottomSheetView.bounds.size.height + bottomSpace
        
        if currentHeight - height <= collapseTransitionThreshold 
            bottomSpace = inferenceViewController!.collapsedHeight - bottomSheetView.bounds.size.height
        
        else if currentHeight - height >= expandThransitionThreshold 
            bottomSpace = 0.0
        
        else 
            bottomSpace = initialBottomSpace
        
        
        return bottomSpace
    
    
    /**
     This method layouts the change of the bottom space of bottom sheet with respect to the view managed by this controller.
     */
    func setBottomSheetLayout(withBottomSpace bottomSpace: CGFloat) 
        
        view.setNeedsLayout()
        bottomSheetViewBottomSpace.constant = bottomSpace
        view.setNeedsLayout()
    
    


CameraFeedManager:

// Copyright 2019 The TensorFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import UIKit
import AVFoundation

// MARK: CameraFeedManagerDelegate Declaration
protocol CameraFeedManagerDelegate: AnyObject 

  /**
  This method delivers the pixel buffer of the current frame seen by the device's camera.
 */
  func didOutput(pixelBuffer: CVPixelBuffer)

  /**
   This method initimates that the camera permissions have been denied.
   */
  func presentCameraPermissionsDeniedAlert()

  /**
   This method initimates that there was an error in video configurtion.
   */
  func presentVideoConfigurationErrorAlert()

  /**
   This method initimates that a session runtime error occured.
   */
  func sessionRunTimeErrorOccured()

  /**
   This method initimates that the session was interrupted.
   */
  func sessionWasInterrupted(canResumeManually resumeManually: Bool)

  /**
   This method initimates that the session interruption has ended.
   */
  func sessionInterruptionEnded()



/**
 This enum holds the state of the camera initialization.
 */
enum CameraConfiguration 

  case success
  case failed
  case permissionDenied


/**
 This class manages all camera related functionality
 */
class CameraFeedManager: NSObject, AVCaptureFileOutputRecordingDelegate 
    
    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?)  // << --- Mine
        print("Video recorded to: " + outputFileURL.absoluteString)
    
    

  // MARK: Camera Related Instance Variables
  private let session: AVCaptureSession = AVCaptureSession()
  private let previewView: PreviewView
  private let sessionQueue = DispatchQueue(label: "sessionQueue")
  private var cameraConfiguration: CameraConfiguration = .failed
  private lazy var videoDataOutput = AVCaptureVideoDataOutput()
  private var movieDataOutput = AVCaptureMovieFileOutput() // << --- Mine
  private var isSessionRunning = false

  // MARK: CameraFeedManagerDelegate
  weak var delegate: CameraFeedManagerDelegate?

  // MARK: Initializer
  init(previewView: PreviewView) 
    self.previewView = previewView
    super.init()

    // Initializes the session
    session.sessionPreset = .high
    self.previewView.session = session
    self.previewView.previewLayer.connection?.videoOrientation = .portrait
    self.previewView.previewLayer.videoGravity = .resizeAspectFill
    self.attemptToConfigureSession()
  

  // MARK: Session Start and End methods

  /**
 This method starts an AVCaptureSession based on whether the camera configuration was successful.
 */
  func checkCameraConfigurationAndStartSession() 
    sessionQueue.async 
      switch self.cameraConfiguration 
      case .success:
        self.addObservers()
        self.startSession()
      case .failed:
        DispatchQueue.main.async 
          self.delegate?.presentVideoConfigurationErrorAlert()
        
      case .permissionDenied:
        DispatchQueue.main.async 
          self.delegate?.presentCameraPermissionsDeniedAlert()
        
      
    
  

  /**
   This method stops a running an AVCaptureSession.
   */
  func stopSession() 
    self.removeObservers()
    sessionQueue.async 
      if self.session.isRunning 
        self.session.stopRunning()
        self.isSessionRunning = self.session.isRunning
      
    

  

  /**
   This method resumes an interrupted AVCaptureSession.
   */
  func resumeInterruptedSession(withCompletion completion: @escaping (Bool) -> ()) 

    sessionQueue.async 
      self.startSession()

      DispatchQueue.main.async 
        completion(self.isSessionRunning)
      
    
  

  /**
 This method starts the AVCaptureSession
 **/
  private func startSession() 
    self.session.startRunning()
    self.isSessionRunning = self.session.isRunning
  

  // MARK: Session Configuration Methods.
  /**
 This method requests for camera permissions and handles the configuration of the session and stores the result of configuration.
 */
  private func attemptToConfigureSession() 
    switch AVCaptureDevice.authorizationStatus(for: .video) 
    case .authorized:
      self.cameraConfiguration = .success
    case .notDetermined:
      self.sessionQueue.suspend()
      self.requestCameraAccess(completion:  (granted) in
        self.sessionQueue.resume()
      )
    case .denied:
      self.cameraConfiguration = .permissionDenied
    default:
      break
    

    self.sessionQueue.async 
      self.configureSession()
    
  

  /**
   This method requests for camera permissions.
   */
  private func requestCameraAccess(completion: @escaping (Bool) -> ()) 
    AVCaptureDevice.requestAccess(for: .video)  (granted) in
      if !granted 
        self.cameraConfiguration = .permissionDenied
      
      else 
        self.cameraConfiguration = .success
      
      completion(granted)
    
  


  /**
   This method handles all the steps to configure an AVCaptureSession.
   */
  private func configureSession() 

    guard cameraConfiguration == .success else 
      return
    
    session.beginConfiguration()

    // Tries to add an AVCaptureDeviceInput.
    guard addVideoDeviceInput() == true else 
      self.session.commitConfiguration()
      self.cameraConfiguration = .failed
      return
    

    // Tries to add an AVCaptureVideoDataOutput.
    guard addVideoDataOutput() else 
      self.session.commitConfiguration()
      self.cameraConfiguration = .failed
      return
    

    session.commitConfiguration()
    self.cameraConfiguration = .success
  

    func startRecording() . // << --- Mine
        self.session.addOutput(movieDataOutput)
        guard let homeDirectory = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first else  return 
        let url = URL(fileURLWithPath: homeDirectory.absoluteString + "/mymovie.mov")
        movieDataOutput.startRecording(to: url , recordingDelegate: self)
    

    func stopRecording()  // <<< -- Mine 
        self.movieDataOutput.stopRecording()
        self.session.removeOutput(movieDataOutput)
    

  /**
 This method tries to an AVCaptureDeviceInput to the current AVCaptureSession.
 */
  private func addVideoDeviceInput() -> Bool 

    /**Tries to get the default back camera.
     */
    guard let camera  = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else 
      return false
    

    do 
      let videoDeviceInput = try AVCaptureDeviceInput(device: camera)
      if session.canAddInput(videoDeviceInput) 
        session.addInput(videoDeviceInput)
        return true
      
      else 
        return false
      
    
    catch 
      fatalError("Cannot create video device input")
    
  

  /**
   This method tries to an AVCaptureVideoDataOutput to the current AVCaptureSession.
   */
  private func addVideoDataOutput() -> Bool 

    let sampleBufferQueue = DispatchQueue(label: "sampleBufferQueue")
    videoDataOutput.setSampleBufferDelegate(self, queue: sampleBufferQueue)
    videoDataOutput.alwaysDiscardsLateVideoFrames = true
    videoDataOutput.videoSettings = [ String(kCVPixelBufferPixelFormatTypeKey) : kCMPixelFormat_32BGRA]

    if session.canAddOutput(videoDataOutput) 
      session.addOutput(videoDataOutput)
      videoDataOutput.connection(with: .video)?.videoOrientation = .portrait
      return true
    
    return false
  

  // MARK: Notification Observer Handling
  private func addObservers() 
    NotificationCenter.default.addObserver(self, selector: #selector(CameraFeedManager.sessionRuntimeErrorOccured(notification:)), name: NSNotification.Name.AVCaptureSessionRuntimeError, object: session)
    NotificationCenter.default.addObserver(self, selector: #selector(CameraFeedManager.sessionWasInterrupted(notification:)), name: NSNotification.Name.AVCaptureSessionWasInterrupted, object: session)
    NotificationCenter.default.addObserver(self, selector: #selector(CameraFeedManager.sessionInterruptionEnded), name: NSNotification.Name.AVCaptureSessionInterruptionEnded, object: session)
  

  private func removeObservers() 
    NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVCaptureSessionRuntimeError, object: session)
    NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVCaptureSessionWasInterrupted, object: session)
    NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVCaptureSessionInterruptionEnded, object: session)
  

  // MARK: Notification Observers
  @objc func sessionWasInterrupted(notification: Notification) 

    if let userInfoValue = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as AnyObject?,
      let reasonIntegerValue = userInfoValue.integerValue,
      let reason = AVCaptureSession.InterruptionReason(rawValue: reasonIntegerValue) 
      print("Capture session was interrupted with reason \(reason)")

      var canResumeManually = false
      if reason == .videoDeviceInUseByAnotherClient 
        canResumeManually = true
       else if reason == .videoDeviceNotAvailableWithMultipleForegroundApps 
        canResumeManually = false
      

      self.delegate?.sessionWasInterrupted(canResumeManually: canResumeManually)

    
  

  @objc func sessionInterruptionEnded(notification: Notification) 

    self.delegate?.sessionInterruptionEnded()
  

  @objc func sessionRuntimeErrorOccured(notification: Notification) 
    guard let error = notification.userInfo?[AVCaptureSessionErrorKey] as? AVError else 
      return
    

    print("Capture session runtime error: \(error)")

    if error.code == .mediaServicesWereReset 
      sessionQueue.async 
        if self.isSessionRunning 
          self.startSession()
         else 
          DispatchQueue.main.async 
            self.delegate?.sessionRunTimeErrorOccured()
          
        
      
     else 
      self.delegate?.sessionRunTimeErrorOccured()

    
  



/**
 AVCaptureVideoDataOutputSampleBufferDelegate
 */
extension CameraFeedManager: AVCaptureVideoDataOutputSampleBufferDelegate 

  /** This method delegates the CVPixelBuffer of the frame seen by the camera currently.
 */
  func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) 

    // Converts the CMSampleBuffer to a CVPixelBuffer.
    let pixelBuffer: CVPixelBuffer? = CMSampleBufferGetImageBuffer(sampleBuffer)

    guard let imagePixelBuffer = pixelBuffer else 
      return
    

    // Delegates the pixel buffer to the ViewController.
    delegate?.didOutput(pixelBuffer: imagePixelBuffer)
  




玩家控制器:

import Foundation

import UIKit
import AVFoundation
import AVKit

class PlayerController : UIViewController 
    override func viewDidLoad() 
        super.viewDidLoad()
    
    
    override func viewDidAppear(_ animated: Bool) 
        super.viewDidAppear(animated)
        guard let homeDirectory = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first else  return 
        let url = URL(fileURLWithPath: homeDirectory.absoluteString + "/mymovie.mov")
        print(url.absoluteString)
        
        let player = AVPlayer(url: url)  // video path coming from above function

        let playerViewController = AVPlayerViewController()
        playerViewController.player = player
        self.present(playerViewController, animated: true) 
            playerViewController.player!.play()
        
    

【问题讨论】:

【参考方案1】:

解决方案是使用以下方法创建路径:

private func documentDirectory() -> String 
        let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory,
                                                                    .userDomainMask,
                                                                    true)
        return documentDirectory[0]
    
    
private func append(toPath path: String,
                        withPathComponent pathComponent: String) -> String? 
        if var pathURL = URL(string: path) 
            pathURL.appendPathComponent(pathComponent)
            
            return pathURL.absoluteString
        
        return nil
    

guard let path = append(toPath: documentDirectory(), withPathComponent: "movie_test.mov") else return

【讨论】:

以上是关于基于 TensorFlow 示例 Swift 录制和播放视频的主要内容,如果未能解决你的问题,请参考以下文章

在 MacOS 上使用 VLCKit 在本地录制流 - 寻找示例

大数据下基于Tensorflow框架的深度学习示例教程

RK3588平台开发系列讲解(AUDIO篇)基于alsa api的音频播放/录制流程

RK3588平台开发系列讲解(AUDIO篇)基于alsa api的音频播放/录制流程

在单独的类中录制和播放语音 (Swift3)

Swift + AVAudioRecorder 非常安静地录制