iOS仿微信拍照和拍视频

2022-08-25  本文已影响0人  小猫仔

公司项目,需要拍照片和拍摄视频,并添加水印。今天这里就先整理一下视频和照片的拍摄,水印另外讲解。
基于模块化思维,拍摄视频相关的方法、功能封装到唯一类中,这个类怎么设计,不用多讲,网上相关介绍很多。这里就讲解功能实现。

基本配置

我们通过AVFoundation框架来实现图片的获取和视频的获取

//捕获设备,通常是前置摄像头,后置摄像头,麦克风(音频输入)
@property (nonatomic ,strong)AVCaptureDevice *device;
//AVCaptureDeviceInput 代表输入设备,他使用AVCaptureDevice 来初始化
@property (nonatomic ,strong)AVCaptureDeviceInput *input;
//当启动摄像头开始捕获输入
@property (nonatomic ,strong)AVCaptureMetadataOutput *output;
//视频文件的输出
@property (nonatomic ,strong)AVCaptureMovieFileOutput *movieOutput;
//照片输出流
@property (nonatomic ,strong)AVCaptureStillImageOutput *ImageOutPut;
////图像预览层,实时显示捕获的图像
@property (nonatomic)AVCaptureVideoPreviewLayer *previewLayer;

获取图片的预览使用的是previewLayer 一个图层,需要添加到目标控制器view的layer上。

//使用AVMediaTypeVideo 指明self.device代表视频,默认使用后置摄像头进行初始化
  self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//使用AVMediaTypeAudio,代表着音频(麦克风),如果需要音频可以不用
  AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
//初始化输入设备
self.input = [[AVCaptureDeviceInput alloc]initWithDevice:self.device error:nil];
AVCaptureDeviceInput *audioInput = [[AVCaptureDeviceInput alloc]initWithDevice:audioDevice error:nil];
//如果需要拍摄视频加上👇一句
 self.movieOutput = [[AVCaptureMovieFileOutput alloc]init];
//self.movieOutput.maxRecordedDuration = maxDuration;//拍摄时长,可以限定也可以无限
//self.movieOutput.minFreeDiskSpaceLimit = 10*1024*1024;//视频最小大小限制,如果限制了,会自动结束视频拍摄
//图片的输出
self.ImageOutPut = [[AVCaptureStillImageOutput alloc]init];
//生产多媒体回话session,通过会话链接输入、输出设备
self.session = [[AVCaptureSession alloc]init];
if ([self.session canSetSessionPreset:AVCaptureSessionPreset1920x1080]) {//设置清晰度
            [self.session setSessionPreset:AVCaptureSessionPreset1920x1080];
    }
//先判断是否可以添加设备,再执行add
if ([self.session canAddInput:self.input]) {
       [self.session addInput:self.input];
     }
if ([self.session canAddInput:audioInput]) {
            [self.session addInput:audioInput];
 }
 if ([self.session canAddOutput:self.ImageOutPut]) {
            [self.session addOutput:self.ImageOutPut];
  }
 if ([self.session canAddOutput:self.movieOutput]) {
            [self.session addOutput:self.movieOutput];
 }
//视频预览图层
self.previewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.session];
[self.previewLayer setBackgroundColor:[UIColor blackColor].CGColor];
[self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[self.previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight];
CGRect layerFrame = CGRectMake(0, 0, kScreenHeight, kScreenWidth);
[self.previewLayer setFrame:layerFrame];
[self.homeVc.view.layer insertSublayer:self.previewLayer atIndex:0];
//修改设备的属性,先加锁
 if ([self.device lockForConfiguration:nil]) {
       //自动白平衡
 if ([self.device isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeAutoWhiteBalance]) {
        [self.device setWhiteBalanceMode:AVCaptureWhiteBalanceModeAutoWhiteBalance];
            }
//解锁
   [self.device unlockForConfiguration];
        }
//开始启动做好拍照和录视频的准备,这个时候就可以在目标控制器上看到镜头采集的数据了
[self.session startRunning];

设备方向配置

[[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(orientationDidChangeNotification:)
                                                 name:UIDeviceOrientationDidChangeNotification
                                               object:nil]
//
if([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft){
        [self.previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight];
}else if([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeRight){
        [self.previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeLeft];
}else{
        
}

拍照

AVCaptureConnection * videoConnection = [self.ImageOutPut connectionWithMediaType:AVMediaTypeVideo];
if (videoConnection ==  nil) {//视频的IO是否正常
        return;
}
AVCaptureVideoOrientation avcaptureOrientation = AVCaptureVideoOrientationLandscapeRight;
if([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft){
        avcaptureOrientation = AVCaptureVideoOrientationLandscapeRight;
}else if([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeRight){
        avcaptureOrientation = AVCaptureVideoOrientationLandscapeLeft;
 }
[videoConnection setVideoOrientation:avcaptureOrientation];
[videoConnection setVideoScaleAndCropFactor:1];
//获取当前帧图片
[self.ImageOutPut captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
if (imageDataSampleBuffer == nil) {
            return;
 }
 NSData *imageData =  [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
//获取当前相机的方向(前还是后)
AVCaptureDevicePosition position = [[self.input device] position];
UIImage *image = [UIImage imageWithData:imageData];
if (position == AVCaptureDevicePositionFront) {
      UIImageOrientation flipImageOrientation = (image.imageOrientation + 3) % 8;
      finalImg = [UIImage imageWithCGImage: image.CGImage scale: image.scale orientation:flipImageOrientation];
 }

水印

UIImage *img = [UIImage imageNamed:@"ic_muyuan_logo"];//水印
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();//创建颜色
//Bitmap上下文
CGContextRef context = CGBitmapContextCreate(NULL, image.size.width, image.size.height, 8, 44 * image.size.width, colorSpace, kCGImageAlphaPremultipliedFirst);
CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);//将image绘至context上下文中
CGContextDrawImage(context, CGRectMake(image.size.width - 200,  image.size.height - 150, 150, 47), img.CGImage);//将img绘至context上下文中
//   CGContextSetRGBFillColor(context, 0.0, 1.0, 1.0, 1);//设置颜色
//Create image ref from the context
CGImageRef imageMasked = CGBitmapContextCreateImage(context);//创建CGImage
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
 UIImage *finalImg = image;
 finalImg = [UIImage imageWithCGImage:imageMasked];//加盖水印的图片

拍摄视频

视频的拍摄需要有开始和结束的操作
1、开始拍摄,需要设定视频文件的临时存放地址

NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSURL *documentDirUrl = [NSURL fileURLWithPath:path isDirectory:YES];
NSURL *fileURL = [NSURL URLWithString:@"movieOut.mp4" relativeToURL:documentDirUrl];
//开启视频拍摄,拍摄的过程中出错和拍摄完成都在delegate中
[self.movieOutput startRecordingToOutputFileURL:fileURL recordingDelegate:self];

2、拍摄过程:

[self.movieOutput stopRecording];完成拍摄
[self.movieOutpu pauseRecording]暂停
[self.movieOutpu resumeRecording]重启拍摄,可以更新文件地址
3、拍摄过程中delegate

//暂停录制
- (void)captureOutput:(AVCaptureFileOutput *)output didPauseRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray<AVCaptureConnection *> *)connections{
    NSLog(@" fileURL is %@",fileURL.path);
    if (self.recordAction) {
        self.recordAction(fileURL, NSError.new);
    }
}
//结束录制、不论是主动完成还是被动停止,只要拍摄停止都会调用这个方法
- (void)captureOutput:(AVCaptureFileOutput *)output didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray<AVCaptureConnection *> *)connections error:(NSError *)error{
    NSLog(@" outputFileURL is %@",outputFileURL.path);
    if (self.recordAction) {
        self.recordAction(outputFileURL, error);
    }
}

长按圆环动画

环形动画很简单,就是两个CAShapeLayer,一个当作背景,一个在画弧度。

backCircleLayer = [CAShapeLayer layer];
backCircleLayer.frame=self.bounds;
backCircleLayer.fillColor=nil;
progressLayer = [CAShapeLayer layer];
progressLayer.frame = self.bounds;
progressLayer.lineCap = kCALineCapRound;
[self.layer addSublayer:backCircleLayer];
[self.layer addSublayer:progressLayer];

backCirclelayer的宽度、颜色、都可以作为属性暴露在接口中,让用户自己定义

-(void)setLineWidth:(CGFloat)lineWidth{
    _lineWidth = lineWidth;
    backCircleLayer.lineWidth = lineWidth;
    progressLayer.lineWidth = lineWidth;
    //
    UIBezierPath * backpath = [UIBezierPath bezierPathWithArcCenter:self.center radius:(CGRectGetWidth(self.bounds)-lineWidth)/2.f startAngle:0 endAngle:M_PI*2
                                                           clockwise:YES];
    backCircleLayer.path = backpath.CGPath;
}
-(void)setStrokeColor:(UIColor *)strokeColor{
    _strokeColor = strokeColor;
    if (_strokeColor) {
        backCircleLayer.strokeColor = strokeColor.CGColor;
    }
}
-(void)setProgressColor:(UIColor *)progressColor{
    _progressColor = progressColor;
    if (_progressColor) {
        progressLayer.strokeColor = progressColor.CGColor;
    }
}

动画的过程,需要通过计时器来累加绘制。👇初始化动画开始的初始弧度和弧度变化步长

preAngle = preInterval/self.duration * 2*M_PI;
    startAngle = -M_PI/2;
   if (!self.clockwise) {
       startAngle = 3 * M_PI/2;
       preAngle = -preAngle;
   }

动画的开始、变化、结束的过程比较简单就不细说了

-(void)start{
    if (_lineWidth<=0) {
        NSLog(@"进度条宽度需大于0");
        return;
    }
    if (_duration<=0) {
        NSLog(@"动画时间需大于0");
        return;
    }
    if (self.timer) {
        [self.timer invalidate];
        self.timer = nil;
    } 
   stopTimer = NO;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:preInterval target:self selector:@selector(changeProgress) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
-(void)changeProgress{
    if (stopTimer) {
        return;
    }
   UIBezierPath *progressPath =  [UIBezierPath bezierPathWithArcCenter:self.center radius:(CGRectGetWidth(self.bounds)-_lineWidth)/2.f startAngle: startAngle endAngle:startAngle+preAngle clockwise:self.clockwise];
    progressLayer.path = progressPath.CGPath;
    NSLog(@"curent Angle is %f",startAngle);
    startAngle = startAngle+preAngle;
    if ( fabs(startAngle - (self.clockwise? -M_PI/2:3*M_PI/2))<1e-6 ) {//判断两个Double值是否相等
        stopTimer = YES;
        [self.timer invalidate];
        self.timer = nil;
    }
}
-(void)end{
    stopTimer = YES;
    if (self.timer) {
        [self.timer invalidate];
        self.timer = nil;
        CGFloat tempflt =self.clockwise? -M_PI/2:3*M_PI/2;
        UIBezierPath *progressPath =  [UIBezierPath bezierPathWithArcCenter:self.center radius:(CGRectGetWidth(self.bounds)-_lineWidth)/2.f startAngle:tempflt endAngle:tempflt clockwise:self.clockwise];
         progressLayer.path = progressPath.CGPath;
    }
}

视频预览和使用

上面讲述了拍照、拍摄视频、进度圆环动画等实现的过程,还缺少最后一点儿,就是怎么预览拍摄的视频,这就需要一个视频播放器,如果只是简单的使用拍摄到的视频,那么使用AVPlayerViewController,就可以展示拍摄的视频。
如果要使用视频,或者要对视频进行二次加工,比如美颜、添加水印、或者添加音乐等等。这就需要在视频播放器上自定义功能按钮,这个时候就需要自定义视频播放器,微信就是这样。
如何创建自定义的视频播放器,网上教程很多,我这里就简单的讲解一下过程步骤(AVPlayer)
1、把网络和本地视频转化为NSURL,加载到播放器

AVPlayerItem *playItem = [AVPlayerItem playerItemWithURL:url];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.frame = view.bounds;
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;//视频填充模式
[view.layer addSublayer:playerLayer];

至于播放、暂停、快进等自定义的控制按钮,需要用户自己在view上展示,视频播放状态需要通过KVO或者NotificationCenter,自己处理。

上一篇下一篇

猜你喜欢

热点阅读