swift 之AVFoundation自定义相机界面拍照、录像、保存到相册、合成视频

2023-11-07

1.**********自定义相机拍照******

 

/**
 自定义相机:
 1.前置和后置摄像头
 typedef NS_ENUM(NSInteger, AVCaptureDevicePosition) {
 AVCaptureDevicePositionUnspecified = 0,
 AVCaptureDevicePositionBack = 1,
 AVCaptureDevicePositionFront = 2
 } NS_AVAILABLE(10_7, 4_0);
 
 2.闪光灯开关
 typedef NS_ENUM(NSInteger, AVCaptureFlashMode) {
 AVCaptureFlashModeOff = 0,
 AVCaptureFlashModeOn = 1,
 AVCaptureFlashModeAuto = 2
 } NS_AVAILABLE(10_7, 4_0);
 
 3.手电筒开关–其实就是相机的闪光灯
 typedef NS_ENUM(NSInteger, AVCaptureTorchMode) {
 AVCaptureTorchModeOff = 0,
 AVCaptureTorchModeOn = 1,
 AVCaptureTorchModeAuto = 2,
 } NS_AVAILABLE(10_7, 4_0);
 
 4.焦距模式调整
 typedef NS_ENUM(NSInteger, AVCaptureFocusMode) {
 AVCaptureFocusModeLocked = 0,
 AVCaptureFocusModeAutoFocus = 1,
 AVCaptureFocusModeContinuousAutoFocus = 2,
 } NS_AVAILABLE(10_7, 4_0);
 
 5.曝光量调节
 typedef NS_ENUM(NSInteger, AVCaptureExposureMode) {
 AVCaptureExposureModeLocked = 0,
 AVCaptureExposureModeAutoExpose = 1,
 AVCaptureExposureModeContinuousAutoExposure = 2,
 AVCaptureExposureModeCustom NS_ENUM_AVAILABLE_IOS(8_0) = 3,
 } NS_AVAILABLE(10_7, 4_0);
 
 6.白平衡
 typedef NS_ENUM(NSInteger, AVCaptureWhiteBalanceMode) {
 AVCaptureWhiteBalanceModeLocked = 0,
 AVCaptureWhiteBalanceModeAutoWhiteBalance = 1,
 AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance = 2,
 } NS_AVAILABLE(10_7, 4_0);
 
 7.距离调整
 typedef NS_ENUM(NSInteger, AVCaptureAutoFocusRangeRestriction) {
 AVCaptureAutoFocusRangeRestrictionNone = 0,
 AVCaptureAutoFocusRangeRestrictionNear = 1,
 AVCaptureAutoFocusRangeRestrictionFar = 2,
 } NS_AVAILABLE_IOS(7_0);

 */
import UIKit

import AVFoundation

import Photos


@available(iOS 10.0, *)
@available(iOS 11.0, *)
class LYBAutoDefineCameraVC: UIViewController ,AVCapturePhotoCaptureDelegate {
    
    var device:AVCaptureDevice!//获取设备:如摄像头

var input:AVCaptureDeviceInput!//输入流

 var photoOutput:AVCapturePhotoOutput! //输出流,(ios10之前用的AVCaptureStillImageOutput,现在已经弃用)
var output:AVCaptureMetadataOutput! //当启动摄像头开始捕获输入

var session:AVCaptureSession!//会话,协调着intput到output的数据传输,input和output的桥梁

var previewLayer:AVCaptureVideoPreviewLayer! //图像预览层,实时显示捕获的图像

var setting:AVCapturePhotoSettings? 图像设置,闪光灯设置
  var photoButton: UIButton?//拍照按钮
   var imageView: UIImageView? //拍照后的成像
  var image: UIImage? //拍照后的成像
var isJurisdiction: Bool?//是否获取了拍照标示
  var flashBtn:UIButton? //闪光灯按钮
    
     var videoConnection: AVCaptureConnection?//捕获链接
    
    var isflash:Bool=false//控制闪光灯开关
    
  override func viewDidLoad() {

  customCamera()//自定义相机
    customUI() //自定义相机按钮
    }

    //    // MARK: - 检查相机权限

    //    func canUserCamear() -> Bool {

    //        //获取相册权限
    //        PHPhotoLibrary.requestAuthorization({ (status) in
    //            switch status {

    //            case .notDetermined:
    //
    //                break

    //            case .restricted://此应用程序没有被授权访问的照片数据

    //                break
    //            case .denied://用户已经明确否认了这一照片数据的应用程序访问
    //                break
    //            case .authorized://已经有权限
    //                break
    //            default:
    //                break
    //            }
    //           return true
    //    }

 //MARK: 初始化自定义相机


    func customCamera(){
        //创建摄像头
        guard let devices = AVCaptureDevice.devices(for: AVMediaType.video) as? [AVCaptureDevice] else { return } //初始化摄像头设备
    guard let devic = devices.filter({ return $0.position == .back }).first else{ return}
        device = devic
        //照片输出设置
        setting=AVCapturePhotoSettings.init(format: [AVVideoCodecKey:AVVideoCodecType.jpeg])
   //用输入设备初始化输入
    self.input = try? AVCaptureDeviceInput(device: device)
    //照片输出流初始化
   self.photoOutput = AVCapturePhotoOutput.init()
    //输出流初始化
     self.output = AVCaptureMetadataOutput.init()
 //生成会话
self.session = AVCaptureSession.init()
    
 //输出画面质量
 if(self.session.canSetSessionPreset(AVCaptureSession.Preset(rawValue: "AVCaptureSessionPreset1280x720"))){
 self.session.sessionPreset = AVCaptureSession.Preset(rawValue: "AVCaptureSessionPreset1280x720")
      }

//添加输入到会话中
  if(self.session.canAddInput(self.input)){

      self.session.addInput(self.input)

}
        
//添加输出到会话中
 if(self.session.canAddOutput(self.photoOutput)){

 self.session.addOutput(self.photoOutput)

    }
        
//使用self.session,初始化预览层,self.session负责驱动input进行信息的采集,layer负责把图像渲染显示
   self.previewLayer = AVCaptureVideoPreviewLayer.init(session: self.session)
    self.previewLayer.frame  = CGRect.init(x: 0, y: 0, width: WIDTH, height: HEIGHT)
   self.previewLayer.videoGravity = AVLayerVideoGravity(rawValue: "AVLayerVideoGravityResizeAspectFill")
     self.view.layer.addSublayer(self.previewLayer)
 


// //自动白平衡
//  if device.isWhiteBalanceModeSupported(.autoWhiteBalance) {
//  device.whiteBalanceMode = .autoWhiteBalance
//   }
// device.unlockForConfiguration()//解锁
//    }
 
        
//启动
self.session.startRunning()
}
    
  //MARK: 添加自定义按钮等UI
  func customUI(){
     //前后摄像头切换
        let changeBtn = UIButton.init()
    changeBtn.frame = CGRect.init(x: Int(WIDTH - 50), y:TopSpaceHigh, width: 40, height: 40)
    changeBtn.setImage(UIImage.init(named: "btn_list_takephotos"), for: UIControl.State.normal)
     changeBtn.addTarget(self, action: #selector(changeCamera), for: .touchUpInside)
  view.addSubview(changeBtn)
      //拍照按钮
   photoButton = UIButton(type: .custom)
   photoButton?.frame = CGRect(x: WIDTH * 1 / 2.0 - 30, y: HEIGHT - 100, width: 60, height: 60)
    photoButton?.setImage(UIImage(named: "icon_shot_sel"), for: .normal)
   photoButton?.addTarget(self, action: #selector(shutterCamera), for: .touchUpInside)
 view.addSubview(photoButton!)

 //闪光灯按钮
 flashBtn = UIButton.init()
 flashBtn?.frame = CGRect.init(x: 10, y:TopSpaceHigh, width: 40, height: 40)
 flashBtn?.addTarget(self, action: #selector(flashAction), for: .touchUpInside)
    flashBtn?.setImage(UIImage.init(named: "icon_flash on_sel"), for: UIControl.State.normal)
  view.addSubview(flashBtn!)

 //取消
  let cancelBtn = UIButton.init()
         cancelBtn.frame = CGRect.init(x: 10, y: HEIGHT - 100, width: 60, height: 60)
  cancelBtn.setTitle("取消", for: .normal)
  cancelBtn.addTarget(self, action: #selector(cancelActin), for: .touchUpInside)
   view.addSubview(cancelBtn)
      }
    
    
    //MARK:前后摄像头更改事件
@objc func changeCamera(){
    //1.获取摄像头,并且改变原有的摄像头
    guard var position = input?.device.position else { return }
    
    //获取当前应该显示的镜头
    position = position == .front ? .back : .front
    //2.重新创建输入设备对象
    //创建新的device
     let devices = AVCaptureDevice.devices(for: AVMediaType.video) as [AVCaptureDevice]
    for devic in devices{
        if devic.position==position{
              device = devic
        }
    }
   
    //input
    guard let videoInput = try? AVCaptureDeviceInput(device: device!) else { return }
    //3. 改变会话的配置前一定要先开启配置,配置完成后提交配置改变
    session.beginConfiguration()
    //移除原有设备输入对象
   session.removeInput(input)
    //添加新的输入对象
    //添加输入到会话中
    if(self.session.canAddInput(videoInput)){
        
        self.session.addInput(videoInput)
        
    }
    //提交会话配置
   session.commitConfiguration()
     //切换
 self.input = videoInput
 }
    
 //MARK:拍照按钮点击事件
  @objc func shutterCamera(){
    videoConnection = photoOutput.connection(with: AVMediaType.video)
         if videoConnection == nil {
                   print("take photo failed!")
                return
             }
    
   photoOutput.capturePhoto(with: setting!, delegate: self)
  }

 //MARK: 闪光灯开关
  @objc func flashAction(){
    isflash = !isflash//改变闪光灯
     try? device.lockForConfiguration()
    if(isflash){//开启
        //开启闪光灯方法一     level取值0-1
//    guard ((try? device.setTorchModeOn(level: 0.5)) != nil) else {
//        print("闪光灯开启出错")
//        return
//    }
 device.torchMode = .on//开启闪光灯方法二
      
    }else{//关闭
        if device.hasTorch {
            device.torchMode = .off//关闭闪光灯
        }
    
    }
       device.unlockForConfiguration()
}
    
   //MARK:取消按钮
 @objc func cancelActin(){
  self.imageView?.removeFromSuperview()
    if(!session.isRunning){
      session.startRunning()
    }
    
  }
// 录制完成输出
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingLivePhotoToMovieFileAt outputFileURL: URL, duration: CMTime, photoDisplayTime: CMTime, resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
    
    
    
     print("paizhao0002")
    
  }
    
    //拍照完成输出--ios10新的
//    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
//        session.stopRunning()//停止
//      let data = photo.fileDataRepresentation();
//   let image=UIImage.init(data: data)
//        print("\(photo.metadata)")
//    }

    
   // //拍照完成输出ios10之前的
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) {
        session.stopRunning()//停止
        let imagedata  =  AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer!, previewPhotoSampleBuffer: previewPhotoSampleBuffer)
        let image=UIImage.init(data: imagedata!)
//        let imageV=UIImageView.init(frame: CGRect.init(x: 0, y:120, width: 100, height: 100))
//        imageV.image=image
//    view.addSubview(imageV)
        
    }
}

 

2.*******************自定义录像和照相***************

/**
 自定义相机:拍照、录像、前后摄像头切换、闪光灯开关、直接保存单个视频片段到相册、多个视频片段合成后保存到系统相册(这个还有点问题,oc的可以)
 1.前置和后置摄像头
 typedef NS_ENUM(NSInteger, AVCaptureDevicePosition) {
 AVCaptureDevicePositionUnspecified = 0,
 AVCaptureDevicePositionBack = 1,
 AVCaptureDevicePositionFront = 2
 } NS_AVAILABLE(10_7, 4_0);
 
 2.闪光灯开关
 typedef NS_ENUM(NSInteger, AVCaptureFlashMode) {
 AVCaptureFlashModeOff = 0,
 AVCaptureFlashModeOn = 1,
 AVCaptureFlashModeAuto = 2
 } NS_AVAILABLE(10_7, 4_0);
 
 3.手电筒开关–其实就是相机的闪光灯
 typedef NS_ENUM(NSInteger, AVCaptureTorchMode) {
 AVCaptureTorchModeOff = 0,
 AVCaptureTorchModeOn = 1,
 AVCaptureTorchModeAuto = 2,
 } NS_AVAILABLE(10_7, 4_0);
 
 4.焦距模式调整
 typedef NS_ENUM(NSInteger, AVCaptureFocusMode) {
 AVCaptureFocusModeLocked = 0,
 AVCaptureFocusModeAutoFocus = 1,
 AVCaptureFocusModeContinuousAutoFocus = 2,
 } NS_AVAILABLE(10_7, 4_0);
 
 5.曝光量调节
 typedef NS_ENUM(NSInteger, AVCaptureExposureMode) {
 AVCaptureExposureModeLocked = 0,
 AVCaptureExposureModeAutoExpose = 1,
 AVCaptureExposureModeContinuousAutoExposure = 2,
 AVCaptureExposureModeCustom NS_ENUM_AVAILABLE_IOS(8_0) = 3,
 } NS_AVAILABLE(10_7, 4_0);
 
 6.白平衡
 typedef NS_ENUM(NSInteger, AVCaptureWhiteBalanceMode) {
 AVCaptureWhiteBalanceModeLocked = 0,
 AVCaptureWhiteBalanceModeAutoWhiteBalance = 1,
 AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance = 2,
 } NS_AVAILABLE(10_7, 4_0);
 
 7.距离调整
 typedef NS_ENUM(NSInteger, AVCaptureAutoFocusRangeRestriction) {
 AVCaptureAutoFocusRangeRestrictionNone = 0,
 AVCaptureAutoFocusRangeRestrictionNear = 1,
 AVCaptureAutoFocusRangeRestrictionFar = 2,
 } NS_AVAILABLE_IOS(7_0);
 
 这里的获取音频轨道和合成后保存到相册还有点问题,
 */
import UIKit

import AVFoundation

import Photos

import AVKit
@available(iOS 10.0, *)
@available(iOS 11.0, *)
class LYBAutoDefineCameraVC: UIViewController ,AVCapturePhotoCaptureDelegate , AVCaptureFileOutputRecordingDelegate{
   
    
    
    var device:AVCaptureDevice!//获取设备:如摄像头
    
    var input:AVCaptureDeviceInput!//输入流
    
    var photoOutput:AVCapturePhotoOutput! //照片输出流,(ios10之前用的AVCaptureStillImageOutput,现在已经弃用)
    var movieoutput:AVCaptureMovieFileOutput! //录像输出流
    
    var session:AVCaptureSession!//会话,协调着intput到output的数据传输,input和output的桥梁
    
    var previewLayer:AVCaptureVideoPreviewLayer! //图像预览层,实时显示捕获的图像
    
    var setting:AVCapturePhotoSettings? 图像设置,
    var photoButton: UIButton?//拍照按钮
    var imageView: UIImageView? //拍照后的成像
    var image: UIImage? //拍照后的成像
    var isJurisdiction: Bool?//是否获取了拍照标示
    var flashBtn:UIButton? //闪光灯按钮
    
    var videoConnection: AVCaptureConnection?//捕获链接
    
    var isflash:Bool=false//控制闪光灯开关
    
    
    
    
    //保存所有的录像片段数组
    var videoAssets = [AVAsset]()
    //保存所有的录像片段url数组
    var assetURLs = [String]()
    //单独录像片段的index索引
    var appendix: Int32 = 1
    
    //最大允许的录制时间(秒)
    let totalSeconds: Float64 = 15.00
    //每秒帧数
    var framesPerSecond:Int32 = 30
    //剩余时间
    var remainingTime : TimeInterval = 15.0
    
    //表示是否停止录像
    var stopRecording: Bool = false
    //剩余时间计时器
    var timer: Timer?
    //进度条计时器
    var progressBarTimer: Timer?
    //进度条计时器时间间隔
    var incInterval: TimeInterval = 0.05
    //进度条
    var progressBar: UIView = UIView()
    //当前进度条终点位置
    var oldX: CGFloat = 0
    //录制、保存按钮
    var recordButton, saveButton : UIButton!
   //视频片段合并后的url
   var outputURL: NSURL?
    
   
    
    override func viewDidLoad() {
        customCamera()//自定义相机
        customUI() //自定义相机按钮
    }
    
    //    // MARK: - 检查相机权限
    
    //    func canUserCamear() -> Bool {
    
    //        //获取相册权限
    //        PHPhotoLibrary.requestAuthorization({ (status) in
    //            switch status {
    
    //            case .notDetermined:
    //
    //                break
    
    //            case .restricted://此应用程序没有被授权访问的照片数据
    
    //                break
    //            case .denied://用户已经明确否认了这一照片数据的应用程序访问
    //                break
    //            case .authorized://已经有权限
    //                break
    //            default:
    //                break
    //            }
    //           return true
    //    }
    
    //MARK: 初始化自定义相机
    
    
    func customCamera(){
        //创建摄像头
        guard let devices = AVCaptureDevice.devices(for: AVMediaType.video) as? [AVCaptureDevice] else { return } //初始化摄像头设备
        guard let devic = devices.filter({ return $0.position == .back }).first else{ return}
        device = devic
        //照片输出设置x
        setting=AVCapturePhotoSettings.init(format: [AVVideoCodecKey:AVVideoCodecType.jpeg])
        //用输入设备初始化输入
        self.input = try? AVCaptureDeviceInput(device: device)
        //照片输出流初始化
        self.photoOutput = AVCapturePhotoOutput.init()
        
        //录像输出流初始化
        self.movieoutput = AVCaptureMovieFileOutput.init()
        //生成会话
        self.session = AVCaptureSession.init()
        
        //输出画面质量
        if(self.session.canSetSessionPreset(AVCaptureSession.Preset(rawValue: "AVCaptureSessionPreset1280x720"))){
            self.session.sessionPreset = AVCaptureSession.Preset(rawValue: "AVCaptureSessionPreset1280x720")
        }
        
        //添加摄像头输入到会话中
        if(self.session.canAddInput(self.input)){
            self.session.addInput(self.input)
            
        }
        //添加一个音频输入设备
        let audioCaptureDevice=AVCaptureDevice.devices(for: AVMediaType.audio).first
        let audioInput=try? AVCaptureDeviceInput.init(device: audioCaptureDevice!)
        if(self.session.canAddInput(audioInput!)){
            self.session.canAddInput(audioInput!)
    
        }

        
        //添加照片输出流到会话中
        if(self.session.canAddOutput(self.photoOutput)){
            
            self.session.addOutput(self.photoOutput)
            
        }
        
        //添加视频输出流到会话中
        if(self.session.canAddOutput(self.movieoutput)){
            self.session.addOutput(self.movieoutput)
        }
       
        
        //使用self.session,初始化预览层,self.session负责驱动input进行信息的采集,layer负责把图像渲染显示
        self.previewLayer = AVCaptureVideoPreviewLayer.init(session: self.session)
        self.previewLayer.frame  = CGRect.init(x: 0, y: 0, width: WIDTH, height: HEIGHT)
        self.previewLayer.videoGravity = AVLayerVideoGravity(rawValue: "AVLayerVideoGravityResizeAspectFill")
        self.view.layer.addSublayer(self.previewLayer)
        
        
        
        // //自动白平衡
        //  if device.isWhiteBalanceModeSupported(.autoWhiteBalance) {
        //  device.whiteBalanceMode = .autoWhiteBalance
        //   }
        // device.unlockForConfiguration()//解锁
        //    }
        
        setupButton()
        //启动
        self.session.startRunning()
        
        //添加进度条
        progressBar.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width,
                                   height: self.view.bounds.height * 0.1)
        progressBar.backgroundColor = UIColor(red: 4, green: 3, blue: 3, alpha: 0.5)
        self.view.addSubview(progressBar)
    }
    
    //MARK: 添加自定义按钮等UI
    func customUI(){
        //前后摄像头切换
        let changeBtn = UIButton.init()
        changeBtn.frame = CGRect.init(x: Int(WIDTH - 50), y:TopSpaceHigh, width: 40, height: 40)
        changeBtn.setImage(UIImage.init(named: "btn_list_takephotos"), for: UIControl.State.normal)
        changeBtn.addTarget(self, action: #selector(changeCamera), for: .touchUpInside)
        view.addSubview(changeBtn)
        //拍照按钮
        photoButton = UIButton(type: .custom)
        photoButton?.frame = CGRect(x: WIDTH * 1 / 2.0 - 30, y: HEIGHT - 100, width: 60, height: 60)
        photoButton?.setImage(UIImage(named: "icon_shot_sel"), for: .normal)
        photoButton?.addTarget(self, action: #selector(shutterCamera), for: .touchUpInside)
        view.addSubview(photoButton!)
        
        //闪光灯按钮
        flashBtn = UIButton.init()
        flashBtn?.frame = CGRect.init(x: 10, y:TopSpaceHigh, width: 40, height: 40)
        flashBtn?.addTarget(self, action: #selector(flashAction), for: .touchUpInside)
        flashBtn?.setImage(UIImage.init(named: "icon_flash on_sel"), for: UIControl.State.normal)
        view.addSubview(flashBtn!)
        
        //取消
        let cancelBtn = UIButton.init()
        cancelBtn.frame = CGRect.init(x: 10, y: HEIGHT - 100, width: 60, height: 60)
        cancelBtn.setTitle("取消", for: .normal)
        cancelBtn.addTarget(self, action: #selector(cancelActin), for: .touchUpInside)
        view.addSubview(cancelBtn)
    }
    
    
    //MARK:前后摄像头更改事件
    @objc func changeCamera(){
        //1.获取摄像头,并且改变原有的摄像头
        guard var position = input?.device.position else { return }
        
        //获取当前应该显示的镜头
        position = position == .front ? .back : .front
        //2.重新创建输入设备对象
        //创建新的device
        let devices = AVCaptureDevice.devices(for: AVMediaType.video) as [AVCaptureDevice]
        for devic in devices{
            if devic.position==position{
                device = devic
            }
        }
        
        //input
        guard let videoInput = try? AVCaptureDeviceInput(device: device!) else { return }
        //3. 改变会话的配置前一定要先开启配置,配置完成后提交配置改变
        session.beginConfiguration()
        //移除原有设备输入对象
        session.removeInput(input)
        //添加新的输入对象
        //添加输入到会话中
        if(self.session.canAddInput(videoInput)){
            
            self.session.addInput(videoInput)
            
        }
        //提交会话配置
        session.commitConfiguration()
        //切换
        self.input = videoInput
    }
    
    //MARK:拍照按钮点击事件
    @objc func shutterCamera(){
        //拍照
        videoConnection = photoOutput.connection(with: AVMediaType.video)
        if videoConnection == nil {
            print("take photo failed!")
            return
        }

        photoOutput.capturePhoto(with: setting!, delegate: self)
        
        
    }
    
    //MARK: 闪光灯开关
    @objc func flashAction(){
        isflash = !isflash//改变闪光灯
        try? device.lockForConfiguration()
        if(isflash){//开启
            //开启闪光灯方法一     level取值0-1
            //    guard ((try? device.setTorchModeOn(level: 0.5)) != nil) else {
            //        print("闪光灯开启出错")
            //        return
            //    }
            device.torchMode = .on//开启闪光灯方法二
            
        }else{//关闭
            if device.hasTorch {
                device.torchMode = .off//关闭闪光灯
            }
            
        }
        device.unlockForConfiguration()
    }
    
    //MARK:取消按钮
    @objc func cancelActin(){
        self.imageView?.removeFromSuperview()
        if(!session.isRunning){
            session.startRunning()
        }
        
    }
   
    
    //    //拍照完成输出--ios10新的
        func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
            session.stopRunning()//停止
          let data = photo.fileDataRepresentation();
            let image=UIImage.init(data: data!)
            print("\(photo.metadata)")
        }
    
    
//    // //拍照完成输出ios10之前的
//    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) {
//        session.stopRunning()//停止
//        let imagedata  =  AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer!, previewPhotoSampleBuffer: previewPhotoSampleBuffer)
//        let image=UIImage.init(data: imagedata!)
//        //        let imageV=UIImageView.init(frame: CGRect.init(x: 0, y:120, width: 100, height: 100))
//        //        imageV.image=image
//        //    view.addSubview(imageV)
//
//    }
    
    
    
    
    
    //创建按钮
    func setupButton(){
        //创建录制按钮
       
        self.recordButton = UIButton(frame:  CGRect.init(x: 0, y: 0, width: 120, height: 50))
        self.recordButton.backgroundColor = UIColor.red;
        self.recordButton.layer.masksToBounds = true
        self.recordButton.setTitle("按住录像", for: UIControl.State.normal)
        self.recordButton.layer.cornerRadius = 20.0
        self.recordButton.layer.position = CGPoint(x: Int(self.view.bounds.width/2),
                                                   y:Int(self.view.bounds.height)-Int(bottomSafeHeight)-150)
        self.recordButton.addTarget(self, action: #selector(onTouchDownRecordButton),
                                    for: .touchDown)
        self.recordButton.addTarget(self, action: #selector(onTouchUpRecordButton),
                                    for: .touchUpInside)
        
        //创建保存按钮
        self.saveButton = UIButton(frame: CGRect.init(x: 0, y: 0, width: 70, height: 50))
      
        self.saveButton.backgroundColor = UIColor.gray;
        self.saveButton.layer.masksToBounds = true
        self.saveButton.setTitle("保存", for: UIControl.State.normal)
        self.saveButton.layer.cornerRadius = 20.0
        
        self.saveButton.layer.position = CGPoint(x: Int(self.view.bounds.width) - 60,
                                                 y:Int(self.view.bounds.height)-Int(bottomSafeHeight)-150)
        self.saveButton.addTarget(self, action: #selector(onClickStopButton),
                                  for: .touchUpInside)
        
        
        //回看按钮
        let backlookBtn:UIButton=UIButton.init(frame: CGRect.init(x: 100, y: 200, width: 100, height: 50))
        backlookBtn.setTitle("回看按钮", for: UIControl.State.normal)
        backlookBtn.addTarget(self, action:#selector(reviewRecord) , for: .touchUpInside)
        view.addSubview(backlookBtn)
        //添加按钮到视图上
        self.view.addSubview(self.recordButton);
        self.view.addSubview(self.saveButton);
    }
    
    //按下录制按钮,开始录制片段
    @objc func  onTouchDownRecordButton(sender: UIButton){
       
        if(!stopRecording) {
            let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory,
                                                            .userDomainMask, true)
            let documentsDirectory = paths[0] as String
            let outputFilePath = "\(documentsDirectory)/output-\(appendix).mov"
            appendix += 1
            let outputURL = NSURL(fileURLWithPath: outputFilePath)
            let fileManager = FileManager.default
            if(fileManager.fileExists(atPath: outputFilePath)) {
                
                do {
                    try fileManager.removeItem(atPath: outputFilePath)
                } catch _ {
                }
            }
            print("开始录制:\(outputFilePath) ")
            movieoutput.startRecording(to: outputURL as URL, recordingDelegate: self as AVCaptureFileOutputRecordingDelegate)
        }
    }
    
    //松开录制按钮,停止录制片段
    @objc func  onTouchUpRecordButton(sender: UIButton){
        if(!stopRecording) {
            timer?.invalidate()
            progressBarTimer?.invalidate()
            movieoutput.stopRecording()
        }
    }
    
    //录像开始的代理方法
    func captureOutput(captureOutput: AVCaptureFileOutput!,
                       didStartRecordingToOutputFileAtURL fileURL: NSURL!,
                       fromConnections connections: [AnyObject]!) {
        startProgressBarTimer()
        startTimer()
    }
    
    //录像结束的代理方法
  
    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
//        //直接保存单个视频片段
//        saveVideoDerectlyToAblum(outputFileURL: outputFileURL)
        
        //******下面是合成处理多个视频片段
        let asset : AVURLAsset = AVURLAsset(url: outputFileURL as URL, options: nil)
        var duration : TimeInterval = 0.0
        duration = CMTimeGetSeconds(asset.duration)
        print("生成视频片段002:\(asset)---\(outputFileURL)")
        videoAssets.append(asset)//所有录像片段数组
        assetURLs.append(outputFileURL.path)///录像的片段url存到数组
        remainingTime = remainingTime - duration

        //到达允许最大录制时间,自动合并视频
        if remainingTime <= 0 {
            mergeVideos()//合并视频
        }
    }
    
    //剩余时间计时器
    func startTimer() {
        timer = Timer(timeInterval: remainingTime, target: self,
                      selector: #selector(timeout), userInfo: nil,
                        repeats:true)
        RunLoop.current.add(timer!, forMode: RunLoop.Mode.default)
    }
    
    //录制时间达到最大时间
    @objc func timeout() {
        stopRecording = true
        print("时间到。")
        movieoutput.stopRecording()
        timer?.invalidate()
        progressBarTimer?.invalidate()
    }
    
    //进度条计时器
    func startProgressBarTimer() {
        progressBarTimer = Timer(timeInterval: incInterval, target: self,
                                   selector: #selector(progress),
                                   userInfo: nil, repeats: true)
        RunLoop.current.add(progressBarTimer!,
                                 forMode: RunLoop.Mode.default)
    }
    
    //修改进度条进度
    @objc func progress() {
        let progressProportion: CGFloat = CGFloat(incInterval / totalSeconds)
        let progressInc: UIView = UIView()
        progressInc.backgroundColor = UIColor(red: 55/255, green: 186/255, blue: 89/255,
                                              alpha: 1)
        let newWidth = progressBar.frame.width * progressProportion
        progressInc.frame = CGRect(x: oldX , y: 0, width: newWidth,
                                   height: progressBar.frame.height)
        oldX = oldX + newWidth
        progressBar.addSubview(progressInc)
    }
    
    //保存按钮点击
    @objc func onClickStopButton(sender: UIButton){
        mergeVideos()
    }
    
    //合并视频片段
    func mergeVideos() {
        let duration = totalSeconds
        
        let composition = AVMutableComposition()
        //合并视频、音频轨道
        let firstTrack = composition.addMutableTrack(
            withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)//获取工程文件中的视频轨道
        let audioTrack = composition.addMutableTrack(
            withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)//获取工程文件中的音频轨道
        
        var insertTime: CMTime = CMTime.zero
        for asset in videoAssets {
            print("合并视频片段001:\(asset)")
        let videoArr  =     asset.tracks(withMediaType: AVMediaType.video)//从素材中获取视频轨道
            do {
                try firstTrack!.insertTimeRange(
                    CMTimeRangeMake(start: CMTime.zero, duration: asset.duration),
                    of: videoArr[0] ,
                    at: insertTime)
            } catch _ {
            }
            
            let audioArr = asset.tracks(withMediaType: AVMediaType.audio)//从素材中获取音频轨道
            print("\(audioArr.count)-----\(videoArr.count)")
            do {
//                try audioTrack!.insertTimeRange(
//                    CMTimeRangeMake(start: CMTime.zero, duration: asset.duration),
//                    of: audioArr[0],
//                    at: insertTime)
            } catch _ {
            }
            
            insertTime = CMTimeAdd(insertTime, asset.duration)
        }
        //旋转视频图像,防止90度颠倒
        firstTrack!.preferredTransform = CGAffineTransform(rotationAngle: CGFloat(Double.pi/2))
        
        //定义最终生成的视频尺寸(矩形的)
        print("视频原始尺寸:", firstTrack!.naturalSize)
        let renderSize = CGSize.init(width: firstTrack!.naturalSize.height, height: firstTrack!.naturalSize.height)
        print("最终渲染尺寸:", renderSize)
        
        //通过AVMutableVideoComposition实现视频的裁剪(矩形,截取正中心区域视频)
        let videoComposition = AVMutableVideoComposition()
        videoComposition.frameDuration = CMTimeMake(value: 1, timescale: framesPerSecond)
        videoComposition.renderSize = renderSize
        
        //视频组合指令
        let instruction = AVMutableVideoCompositionInstruction()
        instruction.timeRange = CMTimeRangeMake(
            start: CMTime.zero,duration: CMTimeMakeWithSeconds(Float64(duration), preferredTimescale: framesPerSecond))

        
        let transformer: AVMutableVideoCompositionLayerInstruction =
            AVMutableVideoCompositionLayerInstruction(assetTrack: firstTrack!)
//        let t1 = CGAffineTransform(translationX: firstTrack!.naturalSize.height,
//                                   y: -(firstTrack!.naturalSize.width-firstTrack!.naturalSize.height)/2)
//        let t2 = CGAffineTransform.init(rotationAngle: CGFloat(Double.pi/2))
//        let finalTransform: CGAffineTransform = t2
//        transformer.setTransform(finalTransform, at: CMTime.zero)
        
        instruction.layerInstructions = [transformer]//视频涂层指令集合
        videoComposition.instructions = [instruction]
        
        //获取合并后的视频路径
        let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory,
                                                                .userDomainMask,true)[0]
        let destinationPath = documentsPath + "/mergeVideo-\(arc4random()%1000).mov"
        print("合并后的视频002:\(destinationPath)")
        let videoPath: NSURL = NSURL(fileURLWithPath: destinationPath as String)
        let exporter = AVAssetExportSession(asset: composition,
                                            presetName:AVAssetExportPresetHighestQuality)!
        exporter.outputURL = videoPath as URL
        exporter.outputFileType = AVFileType.mov
        exporter.videoComposition = videoComposition //设置videoComposition
        exporter.shouldOptimizeForNetworkUse = true
        exporter.timeRange = CMTimeRangeMake(
            start: CMTime.zero,duration: CMTimeMakeWithSeconds(Float64(duration), preferredTimescale: framesPerSecond))
        exporter.exportAsynchronously(completionHandler: {
            print("导出状态\(exporter.status)")
            //将合并后的视频保存到相册
            self.exportDidFinish(session: exporter)
        })
    }
    
    //将合并后的视频保存到相册
    func exportDidFinish(session: AVAssetExportSession) {
        print("视频合并成功!")
        weak var weakSelf=self
         outputURL = session.outputURL! as NSURL
        //将录制好的录像保存到照片库中
    
        PHPhotoLibrary.shared().performChanges({
            PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: weakSelf!.outputURL! as URL)//保存录像到系统相册
        }, completionHandler:{(isSuccess: Bool, error: Error?) in
            DispatchQueue.main.async {
                
                //重置参数,吧碎片视频全部删除
                self.reset()
                
                //弹出提示框
                let alertController = UIAlertController(title: "视频保存成功",
                                                        message: "是否需要回看录像?", preferredStyle: .alert)
                let okAction = UIAlertAction(title: "确定", style: .default, handler: {
                    action in
                    //录像回看
                    weakSelf?.reviewRecord()
                })
                let cancelAction = UIAlertAction(title: "取消", style: .cancel,
                                                 handler: nil)
                alertController.addAction(okAction)
                alertController.addAction(cancelAction)
                self.present(alertController, animated: true,
                             completion: nil)
            }
            }
        )
    }
    
    //视频保存成功,重置各个参数,准备新视频录制
    func reset() {
        //删除视频片段
        for assetURL in assetURLs {
            if(FileManager.default.fileExists(atPath: assetURL)) {
                do {
                    try FileManager.default.removeItem(atPath: assetURL)
                } catch _ {
                }
                print("删除视频片段: \(assetURL)")
            }
        }
        
        //进度条还原
        let subviews = progressBar.subviews
        for subview in subviews {
            subview.removeFromSuperview()
        }
        
        //各个参数还原
        videoAssets.removeAll(keepingCapacity: false)
        assetURLs.removeAll(keepingCapacity: false)
        appendix = 1
        oldX = 0
        stopRecording = false
        remainingTime = totalSeconds
    }
    
    //录像回看
    @objc func reviewRecord() {
        print("录像回放---\(String(describing: outputURL))")
        //视频播放现在两种方式:AVPlayer:自定义UI,目前大多数的第三方播放软件都是基于这个进行封装
//        AVPlayerViewController: 封装好的AVPlayer,可以直接作为视图控制器弹出播放,也可以使用添加view方式使用,不可以自定义UI。
        
        //********通过AVPlayer播放视屏
        //定义一个视频播放器方式一,这个是直接创建,不能设置资源的相关配置
//        let player = AVPlayer(url: outputURL as URL)
//        //创建媒体资源管理对象
//        let palyerItem:AVPlayerItem = AVPlayerItem(url: outputURL! as URL)
//        //创建AVplayer:负责视频播放,方式二,可以设置资源的相关配置,更灵活
//        let player:AVPlayer = AVPlayer.init(playerItem: palyerItem)
//        player.rate = 1.0//播放速度 播放前设置
//        //创建显示视频的图层
//        let playerLayer = AVPlayerLayer.init(player: player)
//        playerLayer.videoGravity = .resizeAspect
//        playerLayer.frame = CGRect.init(x: 100, y: 100, width: 200, height: 200)
//        playerLayer.backgroundColor=UIColor.blue.cgColor
//        self.view.layer.addSublayer(playerLayer)
//        //播放
//        player.play()
        //定义一个视频播放器,通过本地文件路径初始化
        let player = AVPlayer(url: outputURL! as URL)
        let playerViewController:AVPlayerViewController = AVPlayerViewController()
        playerViewController.player = player
        self.present(playerViewController, animated: true) {
            playerViewController.player!.play()
        }
    }
   
    
    
    
   //直接保存录像到照片库,未经过合成
    
    func saveVideoDerectlyToAblum(outputFileURL: URL){
         weak var weakSelf=self
        PHPhotoLibrary.shared().performChanges({
            PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputFileURL as URL)//保存录像到系统相册
        }, completionHandler:{(isSuccess: Bool, error: Error?) in
            DispatchQueue.main.async {
                
                //重置参数,吧碎片视频全部删除
                weakSelf!.reset()
                
                //弹出提示框
                let alertController = UIAlertController(title: "视频保存成功",
                                                        message: "是否需要回看录像?", preferredStyle: .alert)
                let okAction = UIAlertAction(title: "确定", style: .default, handler: {
                    action in
               
                })
                let cancelAction = UIAlertAction(title: "取消", style: .cancel,
                                                 handler: nil)
                alertController.addAction(okAction)
                alertController.addAction(cancelAction)
                self.present(alertController, animated: true,
                             completion: nil)
            }
        }
        )
    }
}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

swift 之AVFoundation自定义相机界面拍照、录像、保存到相册、合成视频 的相关文章

  • unity3D学习之API_Transform 位置

    一 定义 Transform 表示物体的位置 旋转和缩放而且每个对象必备的组件 场景中的每一个物体都有一个Transform 用于储存并操控物体的位置 旋转和缩放 每一个Transform可以有一个父级 允许你分层次应用位置 旋转和缩放 可
  • Qt的事件循环机制

    所有例程和PPT下载 https download csdn net download simonyucsdy 12311712 问题1 Qt中常见的事件有哪些 答 鼠标事件 QMouseEvent 键盘事件 QKeyEvent 绘制事件
  • SAP/FICO/BAPI_ACC_DOCUMENT_POST-外币凭证金额和本位币误差问题处理

    场景 通过BAPI ACC DOCUMENT POST创建USD币别凭证SAP会自动带出CNY本位币金额 本位币金额是USD通过SAP配置的汇率计算出来的 有时候会有误差 想要实现的功能 凭证金额和本位币金额都有外部系统传入设置不需要SAP
  • SQL视图的使用场景/案例

    背景原因 一方面 在一个项目的实际开发过程中牵涉到复杂业务的时候 我们不可避免的需要使用中间表来进行数据连接 一方面 采用Hibernate进行主外键进行关联 多对多 多对一 一对一等 采用主外键关联在数据的操作过程中具有很强的耦合性 尤其
  • gitlab配置ssh密钥及简单使用

    gitlab安装可参考https blog whsir com post 1419 html 演示环境 当前系统Centos6 9 使用IP192 168 0 80 修改gitlab仓库地址 编辑gitlab yml文件 vim opt g
  • 关于安卓调试的log系统

    在安卓系统下 对应用程序的调试 有一个特有的方式 就是log系统 其实就和C语言中的printf函数的使用类似 但是 它是将调试信息存入了缓冲区内 而安卓有四个缓冲区 他的设计模式是模仿了linux内核中的缓冲区模式 将所有的调试信息放入缓
  • Redis的IO多路复用原理

    什么是阻塞 非阻塞 异步同步 select poll epoll 今天我们用一遍文章解开这多年的迷惑 首先我们想要通过网络接收消息 是这样的一个步骤 用户空间向内核空间请求网络数据 内核空间把网卡数据读取到内核缓冲区 将内核缓冲区的数据复制
  • RC正弦波震荡电路

    就开始边写边整理自己的思路吧 毕竟今天刚刚学完 振荡电路是没有输入却有输出的电路 而且是正弦波 就是从白噪声放大 选频得来的 So 可想而知 这个电路需要放大部分 用来放大我们所需的部分 其实是在放大所有频率的 没办法 选频网络 就是两个滤
  • educoder算法设计与分析 实验三 动态规划实验拓展

    实验三 动态规划实验拓展 第1关 聪明的寻宝人 第2关 基因检测 第3关 药剂稀释 第4关 找相似串 第1关 聪明的寻宝人 题目描述 本关任务 计算寻宝人所能带走的宝物的最大价值 一个寻宝人在沙漠中发现一处神秘的宝藏 宝藏中共有n个宝物 n
  • activiti7执行流程详解

    什么是工作流 官方定义 工作流是将一组任务组织起来以完成某个经营过程 定义了任务的触发顺序和触发条件 每个任务可以由一个或多个软件系统完成 也可以由一个或一组人完成 还可以由一个或多个人与软件系统协作完 我的理解 工作流就是针对程序的业务流
  • Vue Grid Layout -️ 适用Vue.js的栅格布局系统,在vue3+上使用

    文章目录 1 官网简介 2 在vue3中使用 1 需要导入vue3支持的版本插件 2 在mian js里引入 3 在组件中使用 3 layout布局的计算逻辑 4 gridLayout 的属性 该栅格系统目前对 vue2 的支持是最好的 v
  • 一文说尽用Python赚钱的五种方法!

    Python是一种非常流行的编程语言 因此Python开发人员可以从众多工作选择中进行选择 您可以学习Python 建立良好的产品组合并成为全职开发人员 也可以选择Python编码作为您的自由职业 我们仅介绍了使用Python赚钱的几种常见
  • 【安全狗】Linux后渗透常见后门驻留方式分析

    引言 当RedTeam拿下了一台服务器并获取到系统较高权限 但不知道服务器的凭证时 RedTeam会采用怎样的技术获取系统凭证呢 又或者 在RedTeam拿下一台服务器 为达到长久控制的目的而专门定制持久化后门 免杀肯定是必须的 的前提下
  • 一张图比較 Docker 和 Git:镜像管理设计理念

    Docker 的镜像管理设计中大量借鉴了 Git 的理念 以下这张图将对两者的核心概念和操作进行比較 有助于大家高速掌握管理 Docker 镜像的正确方式 微信订阅版本号 http mp weixin qq com s biz MzA5MT
  • 前端基础自查

    目录 h5和css3的认知 data v 03da18b4 http部分 前端HTTP优化 网页有哪些部分 服务器渲染 跨域 栅格式布局 阻止冒泡的方法 jq的认知 移动端的适配 不同尺寸屏幕 适配不同浏览器 大屏适配 设计稿 数组重复去重
  • laravel输出HTML内容

    blade模板引擎中的 xxx 表达式的返回值将被自动传递给 PHP 的 htmlentities 函数进行处理 以防止 XSS 攻击 如果需要展示未转义的数据 可以使用 xxx
  • 达梦学习进阶-DM8搭建主备切换

    达梦学习进阶 DM8搭建主备切换 达梦学习进阶 DM8搭建主备切换 随着学习深入 单机架构的达梦很少能满足生产环境的灾备要求 而且单机往往测试库用到的比较多 生产环境还是建议搭建主从或者集群 比较稳妥 所以知识储备还是要做好 今天就来冲击一
  • 论文笔记:Adaptive Graph Convolutional Recurrent Network for Traffic Forecasting

    NIPS 2020 1 abstract intro 当前的大多数深度学习方法基于共享参数设计模型 不同节点对应同一套参数 但是 由于每条道路的具体情况不一样 使用同一套参数无法捕捉细粒度的数据模式 gt 这篇论文设计了节点自适应参数学习
  • M62429L音量控制IC驱动

    前言 最近项目开发中 要控制AV端的音量 由于主控端的AV音量控制gpio被复用为其它功能口了 不得已增加一个ic去控制音量的输出 这里使用的是M62429L音量控制IC 1 M62429音量ic概述 M62429L是一款串行数据控制的双声

随机推荐

  • STM32CubeMx使用教程(四)——定时器中断

    前言 本节课将了解定时器的基本功能及其配置方法 还接触 stm32 中最重要的概念之一 中断 介绍在 cubeMX 中如何对中断进行设置 如何开启中断以及配置中断的优先级等 最后将实现由定时器触发的定时器中断 控制 LED 灯的闪烁 准备工
  • Scientific Toolworks Understand(代码分析软件) v5.1.1001免费版

    Scientific Toolworks Understand 代码分析软件 是一款支持多平台代码分析软件 使用这款Scientific Toolworks Understand 代码分析软件 可以让您根据不同的编译环境对代码进行整体编译
  • 在集群环境中安装R(步骤清晰内容详实,堪称无脑教程!)

    在集群环境下如何解决需要使用R语言的需求 关键词 PBS作业调度系统 HPC R 本文最初思路构思于2018 05 成文于2018 10 17 本文背景是博主的最新一篇论文 上位基因检测机器学习算法创新 临近实验尾声 已经完成在模拟数据中的
  • 【最优潮流】二阶锥松弛在配电网最优潮流计算中的应用(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 1 1 目标函数 1 2 约束条件 1 3 基于二阶锥松弛的模型转换 2 运行结果 2 1 算例分析 2
  • 大数据高频面试题-手写代码

    手写代码 2 1 快排 2 2 归并 2 3 手写Spark WordCount 2 4 冒泡排序 2 5 二分查找 2 6 二叉树之Scala实现 2 6 1 二叉树概念 2 6 2 二叉树的特点 2 6 3 二叉树的Scala代码实现
  • Jmeter的非GUI页面压测命令

    1 压测指令 jmeter bat n t 脚本文件绝对路径 脚本文件 jmx l 结果文件 jtl e o 生成测试报告绝对路径 注意 非GUI压测指令须在Jmeter的bin目录下执行 2 命令解析 n 命令行模式 t 指定jmx脚本地
  • 关于某日头条_signature参数逆向生成纯补环境

    主要是jsvmp里面 练习逆行中 函数的调用和补环境的手法 var window global var document document referrer var location href protocol https var navi
  • bash vim mode

    如果只想在bash中使用vi模式的Readline 在 bashrc 中添加 set o vi
  • 面试题 31:连续子数组的最大和(滴滴的“连续最大和”)

    刚才在笔滴滴的测试开发 编程题第一个就是求连续子数组的最大和问题 这个题在 剑指offer 也有这么一道题 题目描述如下 输入一个整型数组 数组里有正数也有负数 数组中一个或连续多个的多个整数组成一个子数组 求所有子数组的和的最大值 要求时
  • MyBatis学习(六):MyBatis的一对多关联如何操作

    上面一篇文章是关于MyBatis的一对一的关联如何操作 这一篇文章将主要讲述一对多 如何实现 首先还是在上面的一个class表 然后再构建一个student表 一个班级的学生会有很多 因此这就是所说的一对多 要实现的操作就是通过class的
  • K8s为什么需要calico? calico 原理深入理解.

    文章目录 为什么需要calico 网络插件 千千万 为何k8s要用calico calico的架构 calico Pod 跨node通信 tunl0 的作用 为什么所有pod的默认网关都是 169 254 1 1 什么是ARP 代理 jks
  • Springboot使用@Data注解,不用写get/set方法

    Springboot使用 Data注解 不用写get set方法 1 引入依赖
  • 关于项目跟进

    为什么写这个 因为之前自己同时在跟进几个项目 还在做新需求 没有忙过来 自己觉得自己项目那块没有抽出时间做好 所以列下看看哪些流程 是自己需要注意的 这都谈不上项目管理 只能说项目跟进 距离项目经理更是差得远 总之后面会补上这块的知识 激励
  • Ubuntu下安装VS Code以及C/C++插件(PS工作目录的创建)

    参考 Visual Studio Code Ubuntu下安装 以及C C 插件大全 作者 一只青木呀 发布时间 2020 08 05 11 55 53 网址 https blog csdn net weixin 45309916 arti
  • 8.ElasticSearch系列之索引模板与索引

    1 索引模板创建索引 可以通过kibana工具进行创建索引模板 也可以自定义语句 如创建poi索引模板 POST index template poi index patterns poi template settings index n
  • 【Spring】Spring依赖注入与控制反转理解

    Spring是一个庞大的框架 封装了很多成熟的功能 能够让我们无需重复造轮子 其次 它使用IOC进行依赖管理 利用JAVA的反射机制 将实例的初始化交给Spring Spring可以通过配置文件管理实例 我们就不用自己初始化实例啦 有人会问
  • 2021-01-13

    Jacinto 7处理器设备和子系统概述 上 本文概述TI下一代汽车处理器系列的主要架构特征和优势 这张幻灯片列出了Jacinto 7 SoC的不同方面 我将在后续章节中介绍这些内容 这里讨论的特性一般适用于Jacinto 7系列中的所有派
  • [学习日志]伤害生效由谁来决定?

    伤害生效由谁来决定 普通攻击 使用动画事件 用动画事件是最普遍的一种方式 假如我的伤害生效是在动画结束之后呢 攻击动画片段只有2秒时长 要在3秒才对敌方造成伤害 那么动画事件就做不到了 计时器 把伤害生效是点交给计时器去操作 攻击的时候开始
  • 网络拓扑的分类

    一 按网络所覆盖的地理范围分类 1 局域网 LAN 局域网Local Area Network 简称 LAN 是一种私有网络 一般在一座建筑物内或建筑物附近 比如家庭 办公室或工厂 局域网络被广泛用来连接个人计算机和消费类电子设备 通过网络
  • swift 之AVFoundation自定义相机界面拍照、录像、保存到相册、合成视频

    1 自定义相机拍照 自定义相机 1 前置和后置摄像头 typedef NS ENUM NSInteger AVCaptureDevicePosition AVCaptureDevicePositionUnspecified 0 AVCapt