iOS Swift 2 Record Video Avcapturesession

How can i record an mp4 video in mpeg4 container with AVCaptureSession with swift (IOS 8)

First things first: This may not be the best solution, but this is a complete solution.

The code below captures video and audio with AvCaptureSession and converts it into mpeg4 with AvExportSession. There is also zoom in, zoom out and switch camera functionality and permission checking. You can record in 480p or 720p. You can also set minimum and maximum frame rates to create smaller videos. Hope this helps as a complete guide.

Note: There are keys to add to info.plist to ask for camera and photos library permission:

<key>NSCameraUsageDescription</key>
<string>Yo, this is a cam app.</string>

<key>NSPhotoLibraryUsageDescription</key>
<string>Yo, i need to access your photos.</string>

<key>NSMicrophoneUsageDescription</key>
<string>Yo, i can't hear you</string>

And the code:

import UIKit
import Photos
import AVFoundation

class VideoAct: UIViewController, AVCaptureFileOutputRecordingDelegate
{
let captureSession : AVCaptureSession = AVCaptureSession()
var captureDevice : AVCaptureDevice!
var microphone : AVCaptureDevice!
var previewLayer : AVCaptureVideoPreviewLayer!
let videoFileOutput : AVCaptureMovieFileOutput = AVCaptureMovieFileOutput()
var duration : Int = 30
var v_path : URL = URL(fileURLWithPath: "")
var my_timer : Timer = Timer()
var cameraFront : Bool = false
var cameras_number : Int = 0
var max_zoom : CGFloat = 76
var devices : [AVCaptureDevice] = []
var captureInput : AVCaptureDeviceInput = AVCaptureDeviceInput()
var micInput : AVCaptureDeviceInput = AVCaptureDeviceInput()

@IBOutlet weak var cameraView: UIView!

override func viewDidLoad()
{
super.viewDidLoad()
if (check_permissions())
{
initialize()
}
else
{
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo, completionHandler: { (granted) in
if (granted)
{
self.initialize()
}
else
{
self.dismiss(animated: true, completion: nil)
}
})
}
}

func check_permissions() -> Bool
{
return AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo) == AVAuthorizationStatus.authorized
}

@available(iOS 4.0, *)
func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!)
{
//you can implement stopvideoaction here if you want
}

func initialize()
{
let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
v_path = directory.appendingPathComponent("temp_video.mp4")
// we just set the extension .mp4 but
// actually it is a mov file with QT container !! May not play in Android devices.
// it will be ceonverted
self.duration = 30
devices = AVCaptureDevice.devices() as! [AVCaptureDevice]
for device in devices
{
if (device.hasMediaType(AVMediaTypeVideo))
{
if (device.position == AVCaptureDevicePosition.back)
{
captureDevice = device as AVCaptureDevice
}
if (device.position == AVCaptureDevicePosition.front)
{
cameras_number = 2
}
}
if (device.hasMediaType(AVMediaTypeAudio))
{
microphone = device as AVCaptureDevice
}
}
if (cameras_number == 1)
{
//only 1 camera available
btnSwitchCamera.isHidden = true
}
if captureDevice != nil
{
beginSession()
}
max_zoom = captureDevice.activeFormat.videoMaxZoomFactor
}

func beginSession()
{
if (captureSession.isRunning)
{
captureSession.stopRunning()
}
do
{
try captureInput = AVCaptureDeviceInput(device: captureDevice)
try micInput = AVCaptureDeviceInput(device: microphone)
try captureDevice.lockForConfiguration()
}
catch
{
print("errorrrrrrrrrrr \(error)")
}
// beginconfig before adding input and setting settings
captureSession.beginConfiguration()
captureSession.addInput(captureInput)
captureSession.addInput(micInput)
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.connection.videoOrientation = AVCaptureVideoOrientation.init(rawValue: UIDevice.current.orientation.rawValue)!
if (previewLayer.connection.isVideoStabilizationSupported)
{
previewLayer.connection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationMode.auto
}
if (captureDevice.isSmoothAutoFocusSupported)
{
captureDevice.isSmoothAutoFocusEnabled = false
}
if (captureDevice.isFocusModeSupported(AVCaptureFocusMode.continuousAutoFocus))
{
captureDevice.focusMode = .continuousAutoFocus
}
set_preview_size_thing()
set_quality_thing()
if (captureDevice.isLowLightBoostSupported)
{
captureDevice.automaticallyEnablesLowLightBoostWhenAvailable = true
}
if (cameraView.layer.sublayers?[0] is AVCaptureVideoPreviewLayer)
{
//to prevent previewlayers stacking on every camera switch
cameraView.layer.sublayers?.remove(at: 0)
}
cameraView.layer.insertSublayer(previewLayer, at: 0)
previewLayer?.frame = cameraView.layer.frame
captureSession.commitConfiguration()
captureSession.startRunning()
}

func duration_thing()
{
// there is a textview to write remaining time left
self.duration = self.duration - 1
timerTextView.text = "remaining seconds: \(self.duration)"
timerTextView.sizeToFit()
if (self.duration == 0)
{
my_timer.invalidate()
stopVideoAction()
}
}

func switch_cam()
{
captureSession.removeInput(captureInput)
captureSession.removeInput(micInput)
cameraFront = !cameraFront
// capturedevice will be locked again
captureDevice.unlockForConfiguration()
for device in devices
{
if (device.hasMediaType(AVMediaTypeVideo))
{
if (device.position == AVCaptureDevicePosition.back && !cameraFront)
{
captureDevice = device as AVCaptureDevice
}
else if (device.position == AVCaptureDevicePosition.front && cameraFront)
{
captureDevice = device as AVCaptureDevice
}
}
}
beginSession()
}

func zoom_in()
{
// 10x zoom would be enough
if (captureDevice.videoZoomFactor * 1.5 < 10)
{
captureDevice.videoZoomFactor = captureDevice.videoZoomFactor * 1.5
}
else
{
captureDevice.videoZoomFactor = 10
}
}

func zoom_out()
{
if (captureDevice.videoZoomFactor * 0.67 > 1)
{
captureDevice.videoZoomFactor = captureDevice.videoZoomFactor * 0.67
}
else
{
captureDevice.videoZoomFactor = 1
}
}

func set_quality_thing()
{
// there is a switch in the screen (30-30 fps high quality or 15-23 fps normal quality)
// you may not have to do this because export session also has some presets and a property called “optimizefornetwork” or something. But it would be better to make sure the output file is not huge with unnecessary 90 fps video
captureDevice.activeVideoMinFrameDuration = CMTimeMake(1, switch_quality.isOn ? 30 : 15)
captureDevice.activeVideoMaxFrameDuration = CMTimeMake(1, switch_quality.isOn ? 30 : 23)
}

func set_preview_size_thing()
{
//there is a switch for resolution (720p or 480p)
captureSession.sessionPreset = switch_res.isOn ? AVCaptureSessionPreset1280x720 : AVCaptureSessionPreset640x480
//this for loop is probably unnecessary and ridiculous but you can make sure you are using the right format
for some_format in captureDevice.formats as! [AVCaptureDeviceFormat]
{
let some_desc : String = String(describing: some_format)
if (switch_res.isOn)
{
if (some_desc.contains("1280x") && some_desc.contains("720") && some_desc.contains("420v") && some_desc.contains("30 fps"))
{
captureDevice.activeFormat = some_format
break
}
}
else
{
if (some_desc.contains("640x") && some_desc.contains("480") && some_desc.contains("420v"))
{
captureDevice.activeFormat = some_format
break
}
}
}
}

func takeVideoAction()
{
// movieFragmentInterval is important !! or you may end up with a video without audio
videoFileOutput.movieFragmentInterval = kCMTimeInvalid
captureSession.addOutput(videoFileOutput)
(videoFileOutput.connections.first as! AVCaptureConnection).videoOrientation = returnedOrientation()
videoFileOutput.maxRecordedDuration = CMTime(seconds: Double(self.duration), preferredTimescale: 1)
videoFileOutput.startRecording(toOutputFileURL: v_path, recordingDelegate: self)
//timer will tell the remaining time
my_timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(duration_thing), userInfo: nil, repeats: true)
}

func stopVideoAction()
{
captureDevice.unlockForConfiguration()
videoFileOutput.stopRecording()
captureSession.stopRunning()
// turn temp_video into an .mpeg4 (mp4) video
let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let avAsset = AVURLAsset(url: v_path, options: nil)
// there are other presets than AVAssetExportPresetPassthrough
let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough)!
exportSession.outputURL = directory.appendingPathComponent("main_video.mp4")
// now it is actually in an mpeg4 container
exportSession.outputFileType = AVFileTypeMPEG4
let start = CMTimeMakeWithSeconds(0.0, 0)
let range = CMTimeRangeMake(start, avAsset.duration)
exportSession.timeRange = range
exportSession.exportAsynchronously(completionHandler: {
if (exportSession.status == AVAssetExportSessionStatus.completed)
{
// you don’t need temp video after exporting main_video
do
{
try FileManager.default.removeItem(atPath: self.v_path.path)
}
catch
{
}
// v_path is now points to mp4 main_video
self.v_path = directory.appendingPathComponent("main_video.mp4")
self.performSegue(withIdentifier: "ShareVideoController", sender: nil)
}
})
}

func btn_capture_click_listener()
{
if (videoFileOutput.isRecording)
{
stopVideoAction()
}
else
{
takeVideoAction()
}
}

func returnedOrientation() -> AVCaptureVideoOrientation
{
var videoOrientation: AVCaptureVideoOrientation!
let orientation = UIDevice.current.orientation
switch orientation
{
case .landscapeLeft:
videoOrientation = .landscapeRight
case .landscapeRight:
videoOrientation = .landscapeLeft
default:
videoOrientation = .landscapeLeft
}
return videoOrientation
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if (segue.identifier == "ShareVideoController")
{
//to make it visible in the camera roll (main_video.mp4)
PHPhotoLibrary.shared().performChanges({PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: self.v_path)}) { completed, error in}
let destVC : ShareVideoController = segue.destination as! ShareVideoController
// use the path in other screen to upload it or whatever
destVC.videoFilePath = v_path
// bla bla
}
}

override var supportedInterfaceOrientations: UIInterfaceOrientationMask
{
// screen will always be in landscape (remove this override if you want)
return .landscape
}
}

Record crop video square aspect ratio AVCaptureSession

You can record square video using this demo code: https://github.com/DarshanRlogical/DKCustomCamera

OR

You can crop video by using below method:

func manageCroppingToSquare(filePath: URL , completion: @escaping (_ outputURL : URL?) -> ()) {

// output file
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let outputPath = documentsURL?.appendingPathComponent("squareVideo.mov")
if FileManager.default.fileExists(atPath: (outputPath?.path)!) {
do {
try FileManager.default.removeItem(atPath: (outputPath?.path)!)
}
catch {
print ("Error deleting file")
}
}

//input file
let asset = AVAsset.init(url: filePath)
print (asset)
let composition = AVMutableComposition.init()
composition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: kCMPersistentTrackID_Invalid)

//input clip
let clipVideoTrack = asset.tracks(withMediaType: AVMediaTypeVideo)[0]

//make it square
let videoComposition = AVMutableVideoComposition()
videoComposition.renderSize = CGSize(width: CGFloat(clipVideoTrack.naturalSize.height), height: CGFloat(clipVideoTrack.naturalSize.height))
videoComposition.frameDuration = CMTimeMake(1, 30)
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeMakeWithSeconds(60, 30))

//rotate to potrait
let transformer = AVMutableVideoCompositionLayerInstruction(assetTrack: clipVideoTrack)
let t1 = CGAffineTransform(translationX: clipVideoTrack.naturalSize.height, y: -(clipVideoTrack.naturalSize.width - clipVideoTrack.naturalSize.height) / 2)
let t2: CGAffineTransform = t1.rotated(by: .pi/2)
let finalTransform: CGAffineTransform = t2
transformer.setTransform(finalTransform, at: kCMTimeZero)
instruction.layerInstructions = [transformer]
videoComposition.instructions = [instruction]

//exporter
let exporter = AVAssetExportSession.init(asset: asset, presetName: AVAssetExportPresetMediumQuality)
exporter?.outputFileType = AVFileTypeQuickTimeMovie
exporter?.outputURL = outputPath
exporter?.videoComposition = videoComposition

exporter?.exportAsynchronously() { handler -> Void in
if exporter?.status == .completed {
print("Export complete")
DispatchQueue.main.async(execute: {
completion(outputPath)
})
return
} else if exporter?.status == .failed {
print("Export failed - \(String(describing: exporter?.error))")
}
completion(nil)
return
}
}

I hope this code will work for you.

Capturing Video with AVFoundation

I am going to make it easy for you by posting the entire code you need to make a video recorder in AVFoundation. This code should work if you simply copy and paste it as is.

The only things you need to do are:

  1. In the storyboard, connect the camPreview outlet to a UIView in your view controller. This UIView should take up the entire contents of the screen.
  2. Attach the relevant privacy permissions to Info.plist (or else you will only see a black screen)
    • Privacy - Microphone Usage Description
    • Privacy - Camera Usage Description

NOTE: Right at the bottom, I've added how to play the recorded video under the title "Playing the Recorded Video".

EDIT: I forgot two things which made it crash during recording but I have added them now.

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
}

}

This is How You Should Have Setup Your View Controller

Setup your View Controller with campPreview

Permissions for Your Info.plist

plist permissions

Setting Up the Recording Delegates

You need to conform to AVCaptureFileOutputRecordingDelegate. According to Apple docs, it defines an interface for delegates of AVCaptureFileOutput to respond to events that occur in the process of recording a single file. It comes with two methods you need to implement and these are the last two methods at the bottom of the code. The first is,

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


Related Topics



Leave a reply



Submit