美文网首页
iOS仿微信拍照和拍视频

iOS仿微信拍照和拍视频

作者: 小猫仔 | 来源:发表于2022-08-25 09:42 被阅读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,自己处理。

    相关文章

      网友评论

          本文标题:iOS仿微信拍照和拍视频

          本文链接:https://www.haomeiwen.com/subject/jreogrtx.html