了解AVCaptureSession
AVCaptureSession可以被稱作一個資源調(diào)度者(自我理解的!!), 他是用來增加或移除輸入和輸出設(shè)備(真實(shí)的物理設(shè)備被抽象成的虛擬設(shè)備)的, 同時他負(fù)責(zé)開始和停止資源文件的輸入, 同時需要注意的是, 當(dāng)你需要配置AVCaptureSession的其他的時候, 你需要使用[session beginConfiguration];, 然后配置完成之后 調(diào)用 [session commitConfiguration];, 只有當(dāng)commit之后配置才會生效
另外一點(diǎn)是當(dāng)你需要開始的時候[session startRunning]; 這是一個耗時的操作, 你應(yīng)該另開線程來開啟
[session beginConfiguration];
移除原來的輸入/輸出設(shè)備
// 在切換設(shè)備之前, 需要先移除原來的在添加新的
self.session.removeInput(self.videoDeviceInput)
self.session.removeOutput(movieFileOutput)
增加新的輸入/輸出設(shè)備
// 需要首先判斷是否能夠添加相應(yīng)的設(shè)備
// 添加輸入設(shè)備
if self.session.canAddInput(inputDevice) {
self.session.addInput(inputDevice)
} else {
print("can not add the input devices-- \(String(inputDevice))")
}
// 添加輸出(圖片/視頻)
if self.session.canAddOutput(stillImageOutput) {
self.session.addOutput(stillImageOutput)
} else {
print("can not add stillImageOutput !!")
}
設(shè)置畫質(zhì)(low, medium, high, photo,...)
let preset = AVCaptureSessionPresetMedium
}
//判斷是否能夠設(shè)置
if session.canSetSessionPreset(preset) {
session.sessionPreset = preset
}
[session commitConfiguration];
了解 AVCaptureDevice
一個AVCaptureDevice對象相應(yīng)的代表一個輸入物理設(shè)備, 比如iPhone上會有, 前后攝像頭設(shè)備, 音頻輸入設(shè)備, 都被抽象成一個AVCaptureDevice, 通常情況下你會使用AVCaptureDevice提供的一些方法獲取到當(dāng)前支持所有的設(shè)備, 然后通過AVCaptureDevicePositionBack或者AVCaptureDevicePositionFront來區(qū)分前后的攝像頭. 例如, 如下方式獲取到后面的相機(jī)
let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo) as![AVCaptureDevice]
for device in devices {
if device.position == AVCaptureDevicePosition.Back {
return device
}
}
了解AVCaptureDeviceInput
AVCaptureDevice并不是直接添加到AVCaptureSession中的, 而是通過一個AVCaptureDevice初始化一個AVCaptureDeviceInput, 這個時候才相當(dāng)于是AVCaptureSession中的一個輸入設(shè)備, 需要注意的是, 這是一個拋出異常的操作
let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo) as![AVCaptureDevice]
var deviceInput: AVCaptureDeviceInput? = nil
for device in devices {
if device.position == position {
deviceInput = try? AVCaptureDeviceInput(device: device)
break
}
}
了解 AVCaptureVideoPreviewLayer
從后綴名previewLayer就可以猜到, 他是一個CALayer的子類, 適用于你將當(dāng)前的AVCaptureSession輸入設(shè)備采集到的內(nèi)容展示到屏幕上, 所以你可以自定義他的一些屬性, 比如 frame可以自定義位置, videoGravity可以設(shè)置畫面的顯示方式...
private lazy var previewLayer: AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.session)
// set the resize model
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
// add the previewLayer
layer.addSublayer(previewLayer)
clipsToBounds = true
了解AVCaptureOutput
這個適用于我們管理輸出文件的類型, 比如可以選擇是 stillImageOutput, movieFileOutput, 決定我們是要輸出圖片還是視頻, 當(dāng)然同樣需要注意的是, 切換輸出類型的時候需要先移除之前的再添加新的.
private func change(mediaType mediaType: OutputMediaType) {
if mediaType == .stillImage {
self.session.removeOutput(movieFileOutput)
self.session.addOutput(stillImageOutput)
} else {
self.session.removeOutput(stillImageOutput)
self.session.addOutput(movieFileOutput)
}
}
下面介紹一些相機(jī)的配置的操作, 例如閃光燈, 聚焦模式, 曝光模式, 畫面畫質(zhì)... 需要注意的是: 配置之前需要首先申請lockForConfiguration(), 成功之后才開始配置, 配置完成之后應(yīng)該unlockForConfiguration(), 不然可能會影響到其他的設(shè)備lockForConfiguration()
改變 聚焦模式(自動聚焦, 持續(xù)聚焦...), 曝光模式(自動, 持續(xù)...),
private func change(focusModel focusModel: AVCaptureFocusMode, exposureModel: AVCaptureExposureMode, at point: CGPoint, isMonitor: Bool) {
if let device = videoDeviceInput?.device {
do {
// must lock it or it may causes crashing
try device.lockForConfiguration()
if device.focusPointOfInterestSupported && device.isFocusModeSupported(focusModel) {
// 設(shè)置聚焦的點(diǎn),注意(0,0)代表屏幕左上角 (1,1)代表屏幕右下角,同時設(shè)置點(diǎn)之后需要設(shè)置聚焦模式才會生效
device.focusPointOfInterest = point
device.focusMode = focusModel
}
if device.exposurePointOfInterestSupported && device.isExposureModeSupported(exposureModel) {
// only when setting the exposureMode after setting exposurePointOFInterest can be successful
// 設(shè)置曝光的點(diǎn),注意(0,0)代表屏幕左上角 (1,1)代表屏幕右下角,同時設(shè)置點(diǎn)之后需要設(shè)置曝光模式才會生效
device.exposurePointOfInterest = point
device.exposureMode = exposureModel
}
// only when set it true can we receive the AVCaptureDeviceSubjectAreaDidChangeNotification
device.subjectAreaChangeMonitoringEnabled = isMonitor
device.unlockForConfiguration()
} catch {
print("cannot change the focusModel")
}
}
}
改變閃光燈, 注意并不是所有的設(shè)備都有閃光燈, 所以, 設(shè)置之前必須要判斷是否有閃光燈(hasFlash), 并且要判斷是否能支持將要設(shè)置的閃光燈模式(isFlashModeSupported)
private func change(flashModel flashModel: FlashModel) {
let avFlashModel = flashModel.changeToAvFlashModel()
//要判斷是否有閃光燈(hasFlash), 并且要判斷是否能支持將要設(shè)置的閃光燈模式(isFlashModeSupported)
if let trueVideoDevice = videoDeviceInput?.device where trueVideoDevice.hasFlash && trueVideoDevice.isFlashModeSupported(avFlashModel) {
do {
try trueVideoDevice.lockForConfiguration()
trueVideoDevice.flashMode = avFlashModel
trueVideoDevice.unlockForConfiguration()
} catch {
print("can not lock the device for configuration!! ---\(error)")
}
}
}
改變畫質(zhì), 當(dāng)然需要注意的是需要判斷當(dāng)前的設(shè)備是否支持所指定的畫質(zhì), 同時不同的畫質(zhì),是影響當(dāng)前設(shè)備的最大縮放倍數(shù)的device.activeFormat.videoMaxZoomFactor(當(dāng)為1的時候代表不能縮放, 同時操作最大的倍數(shù)時候會crash)
public let AVCaptureSessionPresetPhoto: String
public let AVCaptureSessionPresetHigh: String
public let AVCaptureSessionPresetMedium: String
public let AVCaptureSessionPresetLow: String
public let AVCaptureSessionPreset352x288: String
public let AVCaptureSessionPreset640x480: String
public let AVCaptureSessionPreset1280x720: String
public let AVCaptureSessionPreset1920x1080: String
public let AVCaptureSessionPreset3840x2160: String
public let AVCaptureSessionPresetiFrame960x540: String
public let AVCaptureSessionPresetiFrame1280x720: String
private func change(mediaQuality mediaQuality: MediaQuality) {
if session.canSetSessionPreset(AVCaptureSessionPresetHigh) {
session.sessionPreset = AVCaptureSessionPresetHigh
}
}
最后當(dāng)然需要知道怎么開始采集數(shù)據(jù)和得到采集的數(shù)據(jù)
拍照 需要使用添加到當(dāng)前AVCaptureSession的stillImageOutput來異步開始拍照并且獲得數(shù)據(jù)(格式為CMSampleBuffer, 需要轉(zhuǎn)換為自己需要的格式
self.stillImageOutput?.captureStillImageAsynchronouslyFromConnection(connection, completionHandler: { (buffer, error) in
// 這是采集到的數(shù)據(jù)
if buffer != nil {
// 轉(zhuǎn)換為NSData
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer)
//轉(zhuǎn)換為UIImage
let image = UIImage(data: imageData)
}
}
錄視頻, 錄制視頻是需要首先指定視頻的保存位置的, 同時指定的位置不能是已經(jīng)存在的文件的額位置, 否則將會失敗(下面示例將文件存到沙盒文件的NSTemporaryDirectory文件夾下面), 同時需要設(shè)置代理, 代理方法會有響應(yīng)開始錄制的代理方法和結(jié)束錄制的代理方法(可以判斷是否錄制成功...)
let tempFileName = "\(NSProcessInfo().globallyUniqueString).mov"
let tempFilePath = (NSTemporaryDirectory() as NSString).stringByAppendingPathComponent(tempFileName)
let tempVideoURL = NSURL(fileURLWithPath: tempFilePath)
// 開始錄制, 這里會調(diào)用開始錄制的代理
movieFileOutput?.startRecordingToOutputFileURL(tempVideoURL, recordingDelegate: self)
//結(jié)束錄制, 之后會調(diào)用結(jié)束錄制的代理方法
movieFileOutput?.stopRecording()
AVCaptureFileOutputRecordingDelegate代理方法
// MARK:- public AVCaptureFileOutputRecordingDelegate
extension CameraView: AVCaptureFileOutputRecordingDelegate {
// 開始錄制
public func captureOutput(captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAtURL fileURL: NSURL!, fromConnections connections: [AnyObject]!) {
}
// 完成錄制
public func captureOutput(captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAtURL outputFileURL: NSURL!, fromConnections connections: [AnyObject]!, error: NSError!) {
var success = true
// 需要注意的是, 有時候視頻錄制成功, 這個error仍然不會為nil, 所以通過如下方式需要判斷是否真的錄制成功
if error != nil {// sometimes there may be error but the video is caputed successfully
success = error.userInfo[AVErrorRecordingSuccessfullyFinishedKey] as! Bool
}
// 錄制成功
if (success) {
// 可以把相應(yīng)的文件轉(zhuǎn)到其他的位置
}
}
DEMO 直接下載:https://github.com/jasnig/CameraView