美文网首页
AVFoundation实现小视频录制

AVFoundation实现小视频录制

作者: 我只是个仙 | 来源:发表于2019-11-07 20:22 被阅读0次

    \color{DarkRed}{前景} 模仿微信小视频功能

    • 思路

    利用系统AVCaptureSession来实现,添加video和audio的AVCaptureConnection

    我们先来先看一下AVCaptureSession的官方文档 有一段important的description,一定要单独起一个线程


    官方文档是个好东西

    初始化AVCaptureSession

    self.session = [AVCaptureSession new];
    if ([self.session canSetSessionPreset:AVCaptureSessionPresetHigh]) {
        [self.session setSessionPreset:AVCaptureSessionPresetHigh];
    }
    

    添加两个摄像头

    //摄像头输入
    - (AVCaptureDeviceInput *)backCameraInput {
        if (_backCameraInput == nil) {
            NSError *error;
            _backCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self cameroWithPosition:AVCaptureDevicePositionBack] error:&error];
            if (error) {
                NSLog(@"后置摄像头获取失败");
            }
        }
        return _backCameraInput;
    }
    
    - (AVCaptureDeviceInput *)frontCameraInput {
        if (_frontCameraInput == nil) {
            NSError *error;
            _frontCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self cameroWithPosition:AVCaptureDevicePositionFront] error:&error];
            if (error) {
                NSLog(@"前置摄像头获取失败");
            }
        }
        return _frontCameraInput;
    }
    

    - (void)setupInputs {
    NSError *error = nil;
    if ([self.session canAddInput:self.backCameraInput]) {
        [self.session addInput:self.backCameraInput];
    }
    else {
        [CCHUD showWhiteBackText:kGetLocalizedString(@"添加视频输入失败")];
        return;
    }
    AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio] error:&error];
    if (!audioInput) {
        [CCHUD showWhiteBackText:kGetLocalizedString(@"初始化音频输入设备失败")];
        return;
    }
    if ([self.session canAddInput:audioInput]) {
        [self.session addInput:audioInput];
    }
    else {
        [CCHUD showWhiteBackText:kGetLocalizedString(@"添加音频输入失败")];
        return;
    }
    }
    

    添加两个输出源

    - (void)setupVideoOutput {
    self.videoOutput = [[AVCaptureVideoDataOutput alloc] init];
    [self.videoOutput setSampleBufferDelegate:self queue:self.videoQueue];
    if ([self.session canAddOutput:self.videoOutput]) {
        [self.session addOutput:self.videoOutput];
        self.videoConnection = [self.videoOutput connectionWithMediaType:AVMediaTypeVideo];
        [self.videoConnection setVideoOrientation:AVCaptureVideoOrientationPortrait];
        self.videoConnection.videoMirrored = self.isFront;
    }
    else {
        [CCHUD showWhiteBackText:kGetLocalizedString(@"添加视频输出失败")];
        return;
    }
    self.audioOutput = [[AVCaptureAudioDataOutput alloc] init];
    [self.audioOutput setSampleBufferDelegate:self queue:self.audioQueue];
    
    if ([self.session canAddOutput:self.audioOutput]) {
        [self.session addOutput:self.audioOutput];
        self.audioConnection = [self.audioOutput connectionWithMediaType:AVMediaTypeAudio];
    }
    else {
        [CCHUD showWhiteBackText:kGetLocalizedString(@"添加视频输出失败")];
        return;
    }
    }
    

    想要可见,还需要添加一个AVCaptureVideoPreviewLayer,layer负责绘制output的buffer逐帧绘制

    - (void)setupPreviewLayer {
    self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
    self.previewLayer.frame = CGRectMake(0, WindCS_TOP_HEIGHT, CCScreenWidth, CCScreenHeight - WindCS_IPHONE_X_BOTTOM_HEIGHT - WindCS_TOP_HEIGHT);
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    [self.previewView.layer addSublayer:self.previewLayer];
    self.previewView.layer.masksToBounds = YES;
    }
    

    写出文件 这里还需要一个重要的东西 AVAssetWriter

    官方文档是个好东西

    开始录制

    self.avPath = [[WindCSFilePathManager windCS_CoreVideoPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"%.4f.mp4",[[NSDate new] timeIntervalSince1970]]];
    self.writer =  [AVAssetWriter assetWriterWithURL:[NSURL fileURLWithPath:self.avPath] fileType:AVFileTypeMPEG4 error:nil];
    self.writerVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:[self videoCompressSettings]];
    self.writerVideoInput.expectsMediaDataInRealTime = YES;
    if ([self.writer canAddInput:self.writerVideoInput]) {
        [self.writer addInput:self.writerVideoInput];
    }
    self.writerAudioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:[self audioCompressSettings]];
    self.writerAudioInput.expectsMediaDataInRealTime = YES;
    if ([self.writer canAddInput:self.writerAudioInput]) {
        [self.writer addInput:self.writerAudioInput];
    }
      WindCS_WEAK_SELF
     [self adjustVideoOrientationComplete:^{
        WindCS_STRONG_SELF
        dispatch_async(dispatch_get_main_queue(), ^{
            
            strongSelf.isRecording = YES;
        });
        
    }];
    self.isRecording = YES;
    

    output的配置

    - (NSDictionary *)videoCompressSettings {
    NSDictionary *compressionProperties = @{ AVVideoAverageBitRateKey : @(200 * 8 * 1024),
                                             AVVideoExpectedSourceFrameRateKey: @25,
                                             AVVideoProfileLevelKey : AVVideoProfileLevelH264HighAutoLevel };
    CGFloat height = (CCScreenHeight - WindCS_TOP_HEIGHT - WindCS_IPHONE_X_BOTTOM_HEIGHT);
    return @{ AVVideoCodecKey : AVVideoCodecH264,
              AVVideoWidthKey : @(CCScreenWidth * 2),
              AVVideoHeightKey : @(height * 2),
              AVVideoCompressionPropertiesKey : compressionProperties,
              AVVideoScalingModeKey : AVVideoScalingModeResizeAspectFill };
    }
    
    - (NSDictionary *)audioCompressSettings {
    AudioChannelLayout layout = { .mChannelLayoutTag = kAudioChannelLayoutTag_Stereo,
        .mChannelBitmap = 0,
        .mNumberChannelDescriptions = 0 };
    NSData *channelLayoutData = [NSData dataWithBytes:&layout length:offsetof(AudioChannelLayout, mChannelDescriptions)];
    return @{ AVFormatIDKey: @(kAudioFormatMPEG4AAC),
              AVEncoderBitRateKey : @96000,
              AVSampleRateKey : @44100,
              AVChannelLayoutKey : channelLayoutData,
              AVNumberOfChannelsKey : @2 };
    }
    

    视频方向调整

      /// 调整视频方向
    - (void)adjustVideoOrientationComplete:(void(^)(void))finished {
    UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
    if (orientation == UIDeviceOrientationLandscapeRight) {
        self.writerVideoInput.transform = CGAffineTransformMakeRotation(M_PI / 2.0);
        finished();
       } else if (orientation == UIDeviceOrientationLandscapeLeft) {
           self.writerVideoInput.transform = CGAffineTransformMakeRotation(M_PI * 1.5);
           finished();
       } else if (orientation == UIDeviceOrientationPortraitUpsideDown) {
           self.writerVideoInput.transform = CGAffineTransformMakeRotation(M_PI);
           finished();
       } else if (orientation == UIDeviceOrientationUnknown || orientation == UIDeviceOrientationPortrait) {
           if ([self.cmmotionManager isDeviceMotionAvailable]) {
               self.cmmotionManager.deviceMotionUpdateInterval = 0;
               WindCS_WEAK_SELF
               [self.cmmotionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {
                   double x = motion.gravity.x;
                   double y = motion.gravity.y;
                   double xAngle = atan2(x,y)/M_PI*180.0;
                   WindCS_STRONG_SELF
                   if (xAngle < -45 && xAngle > -135) {
                       strongSelf.writerVideoInput.transform = CGAffineTransformMakeRotation(M_PI * 1.5);
                   } else if (xAngle > 45 && xAngle < 135) {
                       strongSelf.writerVideoInput.transform = CGAffineTransformMakeRotation(M_PI / 2.0);
                   } else if (xAngle >= -45 && xAngle <= 45) {
                        self.writerVideoInput.transform = CGAffineTransformMakeRotation(M_PI);
                    }
                   [strongSelf.cmmotionManager stopDeviceMotionUpdates];
                   finished();
               }];
           }
       }
    }
    

    AVCaptureVideoDataOutputSampleBufferDelegate

    - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    // 录视频
    if (self.isRecording) {
        @synchronized (self) {
            if (self.writer.status == AVAssetWriterStatusUnknown) {
                [self.writer startWriting];
                NSLog(@"self.writer startWriting");
                [self.writer startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
            }
            [self write:sampleBuffer fromConnection:connection];
            
        }
    }
    }
    
    - (void)write:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    if (connection == self.videoConnection) {
        if (self.writerVideoInput.readyForMoreMediaData) {
            [self.writerVideoInput appendSampleBuffer:sampleBuffer];
        }
    }
    else if (connection == self.audioConnection) {
        if (self.writerAudioInput.readyForMoreMediaData) {
            [self.writerAudioInput appendSampleBuffer:sampleBuffer];
        }
    }
    }
    

    停止录制

    dispatch_async(dispatch_get_main_queue(), ^{
        if ([self.writer.inputs containsObject:self.writerVideoInput]) {
            [self.writerVideoInput markAsFinished];
        }
        if ([self.writer.inputs containsObject:self.writerAudioInput]) {
            [self.writerAudioInput markAsFinished];
        }
        [self.writer finishWritingWithCompletionHandler:^{
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"file size : %fM", [self showSize:self.avPath]);
                };
        }];
    });
    
    官方文档是个好东西

    至此,一个小视频录制的功能其实就简单搭建完成了,当然 这里有很多细节,比如:

    • 参数的配置 \color{Peru}{videoCompressSettings} \color{Peru}{audioCompressSettings} 可以根据自己的需要去配置
    • 写出来的视频如何压缩合适?
    • 既然可以逐帧写入,name是否可以逐帧的编辑呢?

    接下来有时间可以一起研究~
    你的喜欢或者关注是我继续的动力哦~

    相关文章

      网友评论

          本文标题:AVFoundation实现小视频录制

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