使用AVFoundation捕获视频

时间:2017-01-17 12:45:04

标签: ios swift avfoundation media

我一直在寻找Stack,我也发现了类似的问题,但没有一个对我有用。我是Swift 3.0的新手。基本上我要做的是使用AVFoundation录制视频。到目前为止,我已设法捕获静止图像,这是我到目前为止的代码

func beginSession() {
    do {
        let deviceInput = try  AVCaptureDeviceInput(device: captureDevice) as AVCaptureDeviceInput
        if captureSession.inputs.isEmpty {
            self.captureSession.addInput(deviceInput)
        }
        stillImageOutput.outputSettings = [AVVideoCodecKey:AVVideoCodecJPEG]

        if captureSession.canAddOutput(stillImageOutput) {
            captureSession.addOutput(stillImageOutput)
        }

    }
    catch {
        print("error: \(error.localizedDescription)")
    }

    guard let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) else {
        print("no preview layer")
        return
    }

    self.view.layer.addSublayer(previewLayer)
    previewLayer.frame = self.view.layer.frame
    captureSession.startRunning()

    // Subviews
    self.view.addSubview(imgOverlay)
    self.view.addSubview(blur)
    self.view.addSubview(label)
    self.view.addSubview(Flip)
    self.view.addSubview(btnCapture)
}

 // SAVE PHOTO
func saveToCamera() {
    if let videoConnection = stillImageOutput.connection(withMediaType: AVMediaTypeVideo) {
        stillImageOutput.captureStillImageAsynchronously(from: videoConnection, completionHandler: { (CMSampleBuffer, Error) in
            if let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(CMSampleBuffer) {
                if let cameraImage = UIImage(data: imageData) {
                    self.flippedImage = UIImage(cgImage: cameraImage.cgImage!, scale: cameraImage.scale, orientation: UIImageOrientation.rightMirrored)
                    UIImageWriteToSavedPhotosAlbum(self.flippedImage, nil, nil, nil)

                }
            }
        })
    }

}

3 个答案:

答案 0 :(得分:71)

通过发布您在AVFoundation中制作录像机所需的全部代码,我将为您提供便利。如果您只是按原样复制并粘贴它,此代码应该有效。您唯一需要记住的是,您需要将camPreview插座连接到StoryBoard中ViewController中的UIView。这个UIView应该占用屏幕的全部内容。我将跟进代码的解释,以便您可以自己调查和修改录像机以满足您的应用程序的需求。您还需要确保将相关的隐私权限附加到info.plist,这些权限是隐私 - 麦克风使用说明和隐私 - 相机使用说明,否则您将只看到黑屏。

注意:在底部,我已经添加了如何在标题"播放录制的视频"下播放录制的视频。

编辑 - 我忘记了录制期间崩溃的两件事,但我现在已经添加了它们。

Swift 4

import UIKit

import AVFoundation

class ViewController: UIViewController, AVCaptureFileOutputRecordingDelegate {

    @IBOutlet weak var camPreview: UIView!

    let cameraButton = UIView()

    let captureSession = AVCaptureSession()

    let movieOutput = AVCaptureMovieFileOutput()

    var previewLayer: AVCaptureVideoPreviewLayer!

    var activeInput: AVCaptureDeviceInput!

    var outputURL: URL!

    override func viewDidLoad() {
        super.viewDidLoad()

        if setupSession() {
            setupPreview()
            startSession()
        }

        cameraButton.isUserInteractionEnabled = true

        let cameraButtonRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.startCapture))

        cameraButton.addGestureRecognizer(cameraButtonRecognizer)

        cameraButton.frame = CGRect(x: 0, y: 0, width: 100, height: 100)

        cameraButton.backgroundColor = UIColor.red

        camPreview.addSubview(cameraButton)

    }

    func setupPreview() {
        // Configure previewLayer
        previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.frame = camPreview.bounds
        previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
        camPreview.layer.addSublayer(previewLayer)
    }

    //MARK:- Setup Camera

    func setupSession() -> Bool {

        captureSession.sessionPreset = AVCaptureSession.Preset.high

        // Setup Camera
        let camera = AVCaptureDevice.default(for: AVMediaType.video)!

        do {

            let input = try AVCaptureDeviceInput(device: camera)

            if captureSession.canAddInput(input) {
                captureSession.addInput(input)
                activeInput = input
            }
        } catch {
            print("Error setting device video input: \(error)")
            return false
        }

        // Setup Microphone
        let microphone = AVCaptureDevice.default(for: AVMediaType.audio)!

        do {
            let micInput = try AVCaptureDeviceInput(device: microphone)
            if captureSession.canAddInput(micInput) {
                captureSession.addInput(micInput)
            }
        } catch {
            print("Error setting device audio input: \(error)")
            return false
        }


        // Movie output
        if captureSession.canAddOutput(movieOutput) {
            captureSession.addOutput(movieOutput)
        }

        return true
    }

    func setupCaptureMode(_ mode: Int) {
        // Video Mode

    }

    //MARK:- Camera Session
    func startSession() {

        if !captureSession.isRunning {
            videoQueue().async {
                self.captureSession.startRunning()
            }
        }
    }

    func stopSession() {
        if captureSession.isRunning {
            videoQueue().async {
                self.captureSession.stopRunning()
            }
        }
    }

    func videoQueue() -> DispatchQueue {
        return DispatchQueue.main
    }

    func currentVideoOrientation() -> AVCaptureVideoOrientation {
        var orientation: AVCaptureVideoOrientation

        switch UIDevice.current.orientation {
            case .portrait:
                orientation = AVCaptureVideoOrientation.portrait
            case .landscapeRight:
                orientation = AVCaptureVideoOrientation.landscapeLeft
            case .portraitUpsideDown:
                orientation = AVCaptureVideoOrientation.portraitUpsideDown
            default:
                 orientation = AVCaptureVideoOrientation.landscapeRight
         }

         return orientation
     }

    @objc func startCapture() {

        startRecording()

    }

    //EDIT 1: I FORGOT THIS AT FIRST

    func tempURL() -> URL? {
        let directory = NSTemporaryDirectory() as NSString

        if directory != "" {
            let path = directory.appendingPathComponent(NSUUID().uuidString + ".mp4")
            return URL(fileURLWithPath: path)
        }

        return nil
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        let vc = segue.destination as! VideoPlaybackViewController

        vc.videoURL = sender as? URL

    }

    func startRecording() {

        if movieOutput.isRecording == false {

            let connection = movieOutput.connection(with: AVMediaType.video)

            if (connection?.isVideoOrientationSupported)! {
                connection?.videoOrientation = currentVideoOrientation()
            }

            if (connection?.isVideoStabilizationSupported)! {
                connection?.preferredVideoStabilizationMode = AVCaptureVideoStabilizationMode.auto
            }

            let device = activeInput.device

            if (device.isSmoothAutoFocusSupported) {

                do {
                    try device.lockForConfiguration()
                    device.isSmoothAutoFocusEnabled = false
                    device.unlockForConfiguration()
                } catch {
                   print("Error setting configuration: \(error)")
                }

            }

            //EDIT2: And I forgot this
            outputURL = tempURL()
            movieOutput.startRecording(to: outputURL, recordingDelegate: self)

            }
            else {
                stopRecording()
            }

       }    

   func stopRecording() {

       if movieOutput.isRecording == true {
           movieOutput.stopRecording()
        }
   }

    func capture(_ captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAt fileURL: URL!, fromConnections connections: [Any]!) {

    }

    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {

        if (error != nil) {

            print("Error recording movie: \(error!.localizedDescription)")

        } else {

            let videoRecorded = outputURL! as URL

            performSegue(withIdentifier: "showVideo", sender: videoRecorded)

        }

    }

}

Swift 3

import UIKit
import AVFoundation

class ViewController: UIViewController, AVCaptureFileOutputRecordingDelegate {

@IBOutlet weak var camPreview: UIView!

let cameraButton = UIView()

let captureSession = AVCaptureSession()

let movieOutput = AVCaptureMovieFileOutput()

var previewLayer: AVCaptureVideoPreviewLayer!

var activeInput: AVCaptureDeviceInput!

var outputURL: URL!

override func viewDidLoad() {
    super.viewDidLoad()

    if setupSession() {
        setupPreview()
        startSession()
    }

    cameraButton.isUserInteractionEnabled = true

    let cameraButtonRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.startCapture))

    cameraButton.addGestureRecognizer(cameraButtonRecognizer)

    cameraButton.frame = CGRect(x: 0, y: 0, width: 100, height: 100)

    cameraButton.backgroundColor = UIColor.red

    camPreview.addSubview(cameraButton)

}

func setupPreview() {
    // Configure previewLayer
    previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
    previewLayer.frame = camPreview.bounds
    previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
    camPreview.layer.addSublayer(previewLayer)
}

//MARK:- Setup Camera

func setupSession() -> Bool {

    captureSession.sessionPreset = AVCaptureSessionPresetHigh

    // Setup Camera
    let camera = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)

    do {
        let input = try AVCaptureDeviceInput(device: camera)
        if captureSession.canAddInput(input) {
            captureSession.addInput(input)
            activeInput = input
        }
    } catch {
        print("Error setting device video input: \(error)")
        return false
    }

    // Setup Microphone
    let microphone = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeAudio)

    do {
        let micInput = try AVCaptureDeviceInput(device: microphone)
        if captureSession.canAddInput(micInput) {
            captureSession.addInput(micInput)
        }
    } catch {
        print("Error setting device audio input: \(error)")
        return false
    }


    // Movie output
    if captureSession.canAddOutput(movieOutput) {
        captureSession.addOutput(movieOutput)
    }

    return true
}

func setupCaptureMode(_ mode: Int) {
        // Video Mode

}

//MARK:- Camera Session
func startSession() {


    if !captureSession.isRunning {
        videoQueue().async {
            self.captureSession.startRunning()
        }
    }
}

func stopSession() {
    if captureSession.isRunning {
        videoQueue().async {
            self.captureSession.stopRunning()
        }
    }
}

func videoQueue() -> DispatchQueue {
    return DispatchQueue.main
}



func currentVideoOrientation() -> AVCaptureVideoOrientation {
    var orientation: AVCaptureVideoOrientation

    switch UIDevice.current.orientation {
    case .portrait:
        orientation = AVCaptureVideoOrientation.portrait
    case .landscapeRight:
        orientation = AVCaptureVideoOrientation.landscapeLeft
    case .portraitUpsideDown:
        orientation = AVCaptureVideoOrientation.portraitUpsideDown
    default:
        orientation = AVCaptureVideoOrientation.landscapeRight
    }

    return orientation
}

func startCapture() {

    startRecording()

}

//EDIT 1: I FORGOT THIS AT FIRST

func tempURL() -> URL? {
    let directory = NSTemporaryDirectory() as NSString

    if directory != "" {
        let path = directory.appendingPathComponent(NSUUID().uuidString + ".mp4")
        return URL(fileURLWithPath: path)
    }

    return nil
}


func startRecording() {

    if movieOutput.isRecording == false {

        let connection = movieOutput.connection(withMediaType: AVMediaTypeVideo)
        if (connection?.isVideoOrientationSupported)! {
            connection?.videoOrientation = currentVideoOrientation()
        }

        if (connection?.isVideoStabilizationSupported)! {
            connection?.preferredVideoStabilizationMode = AVCaptureVideoStabilizationMode.auto
        }

        let device = activeInput.device
        if (device?.isSmoothAutoFocusSupported)! {
            do {
                try device?.lockForConfiguration()
                device?.isSmoothAutoFocusEnabled = false
                device?.unlockForConfiguration()
            } catch {
                print("Error setting configuration: \(error)")
            }

        }

        //EDIT2: And I forgot this
        outputURL = tempURL()
        movieOutput.startRecording(toOutputFileURL: outputURL, recordingDelegate: self)

    }
    else {
        stopRecording()
    }

}

func stopRecording() {

    if movieOutput.isRecording == true {
        movieOutput.stopRecording()
    }
}

func capture(_ captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAt fileURL: URL!, fromConnections connections: [Any]!) {

}

func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) {
    if (error != nil) {
        print("Error recording movie: \(error!.localizedDescription)")
    } else {

        _ = outputURL as URL

    }
    outputURL = nil
}



}

这是您应该如何设置视图控制器

Setup your View Controller with campPreview

您的Info.plist权限

plist permissions

设置录制代表

您需要符合AVCaptureFileOutputRecordingDelegate。根据Apple文档,它为AVCaptureFileOutput的代理定义了一个接口,以响应记录单个文件的过程中发生的事件。它需要实现两种方法,这些是代码底部的最后两种方法。第一个是,

func capture(_ captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAt fileURL: URL!, fromConnections connections: [Any]!) {
}

视频开始录制时,您可以为此添加任何逻辑。在我给出的代码示例中,当您点击左上角的红色方块按钮时,视频开始录制。第二是,

func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) {
    if (error != nil) {
        print("Error recording movie: \(error!.localizedDescription)")
    } else {

        _ = outputURL as URL

    }
    outputURL = nil
}

视频完成录制后调用此方法。在代码示例中,我再次点击红色方块按钮后,视频停止录制。当视频停止录制时,您将获得输出文件URL。这代表了您的视频。您可以使用它来转换到另一个View Controller以在AVPlayer中播放视频。或者你可以保存它。在这个例子中,您会注意到我对输出URL的影响不大。

要开始录制视频,我使用了一个以编程方式创建的按钮,该按钮在左上角显示为红色方块,并响应UITapGesture。您可以在应用中制作更好的按钮。

设置会话

录像机需要我在setupSession()中设置的捕获会话。在这里添加AVCapture输入设备,包括摄像头和麦克风。根据Apple的说法,AVCaptureDeviceInput是AVCaptureInput的具体子类,用于从AVCaptureDevice对象捕获数据。但是,用户需要授予您访问权限,以便在info.plist中添加隐私 - 麦克风使用说明和隐私 - 相机使用说明,并说明您要使用录像机和麦克风的原因。如果你不这样做,你只会得到一个黑屏。会话预设是一个常数值,表示输出的质量等级或比特率。我已将此设置为高,但您可以探索其他选项。 movieOutput的类型为AVCaptureMovieFileOutput,根据Apple,它是AVCaptureFileOutput的具体子类,用于将数据捕获到QuickTime影片。这实际上是允许您录制和保存视频的。

设置预览

这是您在setupPreview()中设置相机预览图层的位置。使用以下AVCaptureVideoPreviewLayer(session:captureSession)创建的捕获会话设置预览图层。

开始会话

最后一步是启动在startSession()中完成的会话。您检查会话是否已在运行,如果不是,则启动会话。

if !captureSession.isRunning {
    videoQueue().async {
        self.captureSession.startRunning()
    }
}

开始录制

点击红色按钮时,会调用startRecording()方法。在这里,我添加了处理视频方向和视频稳定的方法。最后,我们再次看到我们在会话中设置的movieOutput变量。我们称之为将我们的电影录制到outputURL并告诉它我们处理录制开始和结束的委托方法是在同一个视图控制器中(最后两种方法)。

停止录制

恰巧当您再次点击红色按钮时,再次调用startRecoding,但会注意到正在记录某些内容并调用stopRecording。

播放录制的视频

我今天很慷慨,所以我也会把它扔掉。

创建一个新的视图控制器并将其命名为VideoPlayback。使用Storyboard中的segue将其与第一个ViewController连接。给segue一个标识符" showVideo"。创建一个UIView并填满VideoPlayback的屏幕并为其视图控制器创建一个名为videoView的插座。将以下代码添加到新的VideoPlayback视图控制器:

Swift 4

import UIKit
import AVFoundation

class VideoPlayback: UIViewController {

    let avPlayer = AVPlayer()
    var avPlayerLayer: AVPlayerLayer!

    var videoURL: URL!
    //connect this to your uiview in storyboard
    @IBOutlet weak var videoView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        avPlayerLayer = AVPlayerLayer(player: avPlayer)
        avPlayerLayer.frame = view.bounds
        avPlayerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
        videoView.layer.insertSublayer(avPlayerLayer, at: 0)

        view.layoutIfNeeded()

        let playerItem = AVPlayerItem(url: videoURL as URL)
        avPlayer.replaceCurrentItem(with: playerItem)

        avPlayer.play()
    }
}

Swift 3

import UIKit
import AVFoundation

class VideoPlayback: UIViewController {

    let avPlayer = AVPlayer()
    var avPlayerLayer: AVPlayerLayer!

    var videoURL: URL!
    //connect this to your uiview in storyboard
    @IBOutlet weak var videoView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        avPlayerLayer = AVPlayerLayer(player: avPlayer)
        avPlayerLayer.frame = view.bounds
        avPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
        videoView.layer.insertSublayer(avPlayerLayer, at: 0)

        view.layoutIfNeeded()

        let playerItem = AVPlayerItem(url: videoURL as URL)
        avPlayer.replaceCurrentItem(with: playerItem)

        avPlayer.play()
    }
}

现在返回上一个委托方法并按如下方式进行修改:

func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) {

    if (error != nil) {
        print("Error recording movie: \(error!.localizedDescription)")
    } else {

        let videoRecorded = outputURL! as URL

        performSegue(withIdentifier: "showVideo", sender: videoRecorded)
    }
}

最后,为segue方法创建一个准备工作,该方法将初始化将与AVPlayer一起播放的videoURL。

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    let vc = segue.destination as! VideoPlayback
    vc.videoURL = sender as! URL
}

现在进行测试,返回并开始录制视频。在红色方块的第二个水龙头上,将执行segue,您将看到录制的视频自动播放。

答案 1 :(得分:3)

基于@gwinyai的惊人答案我做了一个类似的Camera框架。 https://github.com/eonist/HybridCamera这也支持拍照,并且没有由@Maksim Kniazev描述的由此行引起的故障问题:

if (connection?.isVideoStabilizationSupported)! {
    connection?.preferredVideoStabilizationMode = AVCaptureVideoStabilizationMode.auto
}

答案 2 :(得分:2)

@gwinyai的答案已更新为(swift 4)的最新版本

import UIKit
import AVFoundation


class ViewController: UIViewController, AVCaptureFileOutputRecordingDelegate {

@IBOutlet weak var camPreview: UIView!
let captureSession = AVCaptureSession()
let movieOutput = AVCaptureMovieFileOutput()

var previewLayer: AVCaptureVideoPreviewLayer!
var activeInput: AVCaptureDeviceInput!
var outputURL: URL!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    if setupSession() {
        setupPreview()
        startSession()
    }
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}
func setupPreview() {
    // Configure previewLayer
    previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
    previewLayer.frame = camPreview.bounds
    previewLayer.videoGravity = .resizeAspectFill
    camPreview.layer.addSublayer(previewLayer)
}

//MARK:- Setup Camera

func setupSession() -> Bool {
    captureSession.sessionPreset = AVCaptureSession.Preset.high

    // Setup Camera
    let camera = AVCaptureDevice.default(for: .video)

    do {
        let input = try AVCaptureDeviceInput(device: camera!)
        if captureSession.canAddInput(input) {
            captureSession.addInput(input)
            activeInput = input
        }
    } catch {
        print("Error setting device video input: \(error)")
        return false
    }

    // Setup Microphone
    let microphone = AVCaptureDevice.default(for: .audio)

    do {
        let micInput = try AVCaptureDeviceInput(device: microphone!)
        if captureSession.canAddInput(micInput) {
            captureSession.addInput(micInput)
        }
    } catch {
        print("Error setting device audio input: \(error)")
        return false
    }


    // Movie output
    if captureSession.canAddOutput(movieOutput) {
        captureSession.addOutput(movieOutput)
    }

    return true
}

func setupCaptureMode(_ mode: Int) {
    // Video Mode

}

//MARK:- Camera Session
func startSession() {


    if !captureSession.isRunning {
        videoQueue().async {
            self.captureSession.startRunning()
        }
    }
}

func stopSession() {
    if captureSession.isRunning {
        videoQueue().async {
            self.captureSession.stopRunning()
        }
    }
}

func videoQueue() -> DispatchQueue {
    return DispatchQueue.main
}



func currentVideoOrientation() -> AVCaptureVideoOrientation {
    var orientation: AVCaptureVideoOrientation

    switch UIDevice.current.orientation {
    case .portrait:
        orientation = AVCaptureVideoOrientation.portrait
    case .landscapeRight:
        orientation = AVCaptureVideoOrientation.landscapeLeft
    case .portraitUpsideDown:
        orientation = AVCaptureVideoOrientation.portraitUpsideDown
    default:
        orientation = AVCaptureVideoOrientation.landscapeRight
    }

    return orientation
}

func startCapture() {

    startRecording()

}

//EDIT 1: I FORGOT THIS AT FIRST

func tempURL() -> URL? {
    let directory = NSTemporaryDirectory() as NSString

    if directory != "" {
        let path = directory.appendingPathComponent(NSUUID().uuidString + ".mp4")
        return URL(fileURLWithPath: path)
    }

    return nil
}


func startRecording() {

    if movieOutput.isRecording == false {

        let connection = movieOutput.connection(with: .video)
        if (connection?.isVideoOrientationSupported)! {
            connection?.videoOrientation = currentVideoOrientation()
        }

        if (connection?.isVideoStabilizationSupported)! {
            connection?.preferredVideoStabilizationMode = AVCaptureVideoStabilizationMode.auto
        }

        let device = activeInput.device
        if (device.isSmoothAutoFocusSupported) {
            do {
                try device.lockForConfiguration()
                device.isSmoothAutoFocusEnabled = false
                device.unlockForConfiguration()
            } catch {
                print("Error setting configuration: \(error)")
            }

        }

        //EDIT2: And I forgot this
        outputURL = tempURL()
        movieOutput.startRecording(to: outputURL, recordingDelegate: self)

    }
    else {
        stopRecording()
    }

}

func stopRecording() {

    if movieOutput.isRecording == true {
        movieOutput.stopRecording()
    }
}

func fileOutput(_ output: AVCaptureFileOutput, didStartRecordingTo fileURL: URL, from connections: [AVCaptureConnection]) {

}

func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
    if (error != nil) {
        print("Error recording movie: \(error!.localizedDescription)")
    } else {
        _ = outputURL as URL
    }
    outputURL = nil
}

}