美文网首页iOSiOS音视频开发经验之路
iOS小视频录制(类似微信)

iOS小视频录制(类似微信)

作者: 低调的魅力 | 来源:发表于2017-07-02 12:21 被阅读325次

    前言:前一段时间产品新需求,说是要做微信的小时频功能.
    功能需求:360x640 or 640x360的分辨率,大小在2MB 之内的 MP4格式
    短视频 demo 地址
    思路:使用 AVFoundtion框架提供的 AVCaptureSession进行录制,
    大致如下:
    摄像头,麦克风(input)->AVCapture Session->AVCaptureVideoDataOutpu(仅视频数据,无音频数据)t,AVCaptureAudioDataOutput(音频数据),AVCaptureStillImageOutput(照片),AVCaptureMovieFileOutput(有视频,也有音频)->AVAssetWriter封装视频和音频格式,写入磁盘,完成录制

    关系图.jpg

    1.AVCaptureDeviceInput(输入设备):包括摄像头(前置,后置),麦克风,负责采集视频和音频数据
    2.AVCaptureVideoDataOutpu:输出视频数据,仅仅只是视频,没有声音,可以对视频格式和参数进行自定义封装和添加滤镜处理
    3.AVCaptureAudioDataOutput:输出音频数据,可以对格式和参数进行自定义封装
    4.AVCaptureStillImageOutput:照片数据,用户获取静态图像(拍照)
    5.AVCaptureMovieFileOutput:输出完整视频(包括音频),优点:录制简单,缺点:可定制性差,短视频不适用(体积太大)
    6.AVCapture Session:用于处理输入与输出之间的数据流
    7.AVCaptureVideoPreviewLayer:摄像头实时预览 layer, 可以预览摄像头采集的实时视频信息
    8.AVAssetWriter:封装音视频格式,写入磁盘

    代码示例:

    1.录制:
    <pre>

    • (AVCaptureSession *)recordSession {
      if (_recordSession == nil) {
      _recordSession = [[AVCaptureSession alloc] init];
      _recordSession.sessionPreset = AVCaptureSessionPresetHigh;
      //添加后置摄像头的输出
      if ([_recordSession canAddInput:self.backCameraInput]) {
      [_recordSession addInput:self.backCameraInput];
      }
      //添加后置麦克风的输出
      if ([_recordSession canAddInput:self.audioMicInput]) {
      [_recordSession addInput:self.audioMicInput];
      }
      //添加视频输出
      if ([_recordSession canAddOutput:self.videoOutput]) {
      [_recordSession addOutput:self.videoOutput];
      _cx = VIDEO_WIDTH;
      _cy = VIDEO_HEIGHT;
      }
      //添加音频输出
      if ([_recordSession canAddOutput:self.audioOutput]) {
      [_recordSession addOutput:self.audioOutput];
      }
      // 静态图像输出
      if ([_recordSession canAddOutput:self.stillImageOutput]) {
      [_recordSession addOutput:self.stillImageOutput];
      }
      //设置视频录制的方向
      self.videoConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
      }
      return _recordSession;
      }

    //后置摄像头输入

    • (AVCaptureDeviceInput *)backCameraInput {
      if (_backCameraInput == nil) {
      NSError *error;
      _backCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self backCamara] error:&error];
      if (error) {
      NSLog(@"获取后置摄像头失败~");
      }
      }
      return _backCameraInput;
      }

    //前置摄像头输入

    • (AVCaptureDeviceInput *)frontCameraInput {
      if (_frontCameraInput == nil) {
      NSError *error;
      _frontCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self frontCamara] error:&error];
      if (error) {
      NSLog(@"获取前置摄像头失败~");
      }
      }
      return _frontCameraInput;
      }

    //麦克风输入

    • (AVCaptureDeviceInput *)audioMicInput {
      if (_audioMicInput == nil) {
      AVCaptureDevice *mic = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
      NSError *error;
      _audioMicInput = [AVCaptureDeviceInput deviceInputWithDevice:mic error:&error];
      if (error) {
      NSLog(@"获取麦克风失败~");
      }
      }
      return _audioMicInput;
      }

    //视频输出

    • (AVCaptureVideoDataOutput )videoOutput {
      if (_videoOutput == nil) {
      _videoOutput = [[AVCaptureVideoDataOutput alloc] init];
      [_videoOutput setSampleBufferDelegate:self queue:self.captureQueue];
      NSDictionary
      setcapSettings = [NSDictionary dictionaryWithObjectsAndKeys:
      [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange], kCVPixelBufferPixelFormatTypeKey,
      nil];
      _videoOutput.videoSettings = setcapSettings;
      }
      return _videoOutput;
      }

    //音频输出

    • (AVCaptureAudioDataOutput *)audioOutput {
      if (_audioOutput == nil) {
      _audioOutput = [[AVCaptureAudioDataOutput alloc] init];
      [_audioOutput setSampleBufferDelegate:self queue:self.captureQueue];
      }
      return _audioOutput;
      }

    //静态图像输出

    • (AVCaptureStillImageOutput *)stillImageOutput{
      if (_stillImageOutput == nil) {
      _stillImageOutput = [[AVCaptureStillImageOutput alloc]init];
      _stillImageOutput.outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
      }
      return _stillImageOutput;
      }
      //视频连接
    • (AVCaptureConnection *)videoConnection {
      _videoConnection = [self.videoOutput connectionWithMediaType:AVMediaTypeVideo];
      return _videoConnection;
      }

    //音频连接

    • (AVCaptureConnection *)audioConnection {
      if (_audioConnection == nil) {
      _audioConnection = [self.audioOutput connectionWithMediaType:AVMediaTypeAudio];
      }
      return _audioConnection;
      }

    //捕获到的视频呈现的layer

    • (AVCaptureVideoPreviewLayer *)previewLayer {
      if (_previewLayer == nil) {
      //通过AVCaptureSession初始化
      AVCaptureVideoPreviewLayer *preview = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.recordSession];
      //设置比例为铺满全屏
      preview.videoGravity = AVLayerVideoGravityResizeAspectFill;
      _previewLayer = preview;
      }
      return _previewLayer;
      }

    //录制的队列

    • (dispatch_queue_t)captureQueue {
      if (_captureQueue == nil) {
      _captureQueue = dispatch_queue_create("cn.qiuyouqun.im.wclrecordengine.capture", DISPATCH_QUEUE_SERIAL);
      }
      return _captureQueue;
      }

    </pre>

    2.格式封装和配置参数
    <pre>
    //初始化视频输入

    • (void)initVideoInputHeight:(NSInteger)cy width:(NSInteger)cx {
      //录制视频的一些配置,分辨率,编码方式等等
      NSInteger numPixels = cx * cy;
      //每像素比特
      CGFloat bitsPerPixel = 6.0;
      NSInteger bitsPerSecond = numPixels * bitsPerPixel;

      // 码率和帧率设置
      NSDictionary compressionProperties = @{ AVVideoAverageBitRateKey:@(bitsPerSecond),
      AVVideoExpectedSourceFrameRateKey:@(30),
      AVVideoMaxKeyFrameIntervalKey:@(30),
      AVVideoProfileLevelKey:AVVideoProfileLevelH264BaselineAutoLevel };
      NSDictionary
      settings = @{AVVideoCodecKey:AVVideoCodecH264,
      AVVideoScalingModeKey:AVVideoScalingModeResizeAspectFill,
      AVVideoWidthKey:@(cx),
      AVVideoHeightKey:@(cy),
      AVVideoCompressionPropertiesKey:compressionProperties };

      //初始化视频写入类
      _videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:settings];
      _videoInput.transform = [self transformFromCurrentVideoOrientationToOrientation:AVCaptureVideoOrientationPortrait];
      //表明输入是否应该调整其处理为实时数据源的数据
      _videoInput.expectsMediaDataInRealTime = YES;
      //将视频输入源加入
      if ([_writer canAddInput:_videoInput]) {
      [_writer addInput:_videoInput];
      }
      }

    //初始化音频输入

    • (void)initAudioInputChannels:(int)ch samples:(Float64)rate {
      //音频的一些配置包括音频各种这里为AAC,音频通道、采样率和音频的比特率
      NSDictionary *settings = @{AVEncoderBitRatePerChannelKey:@(28000),
      AVFormatIDKey:@(kAudioFormatMPEG4AAC),
      AVNumberOfChannelsKey:@(1),
      AVSampleRateKey:@(22050) };
      //初始化音频写入类
      _audioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:settings];
      //表明输入是否应该调整其处理为实时数据源的数据
      _audioInput.expectsMediaDataInRealTime = YES;
      //将音频输入源加入
      [_writer addInput:_audioInput];
      }
      </pre>

    3.视频方向控制
    <pre>

    • (CMMotionManager *)motionManager{
      if (!_motionManager) {
      _motionManager = [[CMMotionManager alloc]init];
      _motionManager.deviceMotionUpdateInterval = MOTION_UPDATE_INTERVAL;
      }
      return _motionManager;
      }
      // 开始
    • (void)startDeviceMotionUpdates{
      if (_motionManager.deviceMotionAvailable) {
      [_motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {
      [self performSelectorOnMainThread:@selector(handleDeviceMotion:) withObject:motion waitUntilDone:YES];
      }];
      }
      }
      // 结束
    • (void)stopDeviceMotionUpdates{
      [_motionManager stopDeviceMotionUpdates];
      }
    • (void)handleDeviceMotion:(CMDeviceMotion *)deviceMotion{
      double x = deviceMotion.gravity.x;
      double y = deviceMotion.gravity.y;
      if (fabs(y) >= fabs(x))
      {
      if (y >= 0){
      _deviceOrientation = UIDeviceOrientationPortraitUpsideDown;
      _videoOrientation = AVCaptureVideoOrientationPortraitUpsideDown;
      //NSLog(@"UIDeviceOrientationPortraitUpsideDown--AVCaptureVideoOrientationPortraitUpsideDown");
      }
      else{
      _deviceOrientation = UIDeviceOrientationPortrait;
      _videoOrientation = AVCaptureVideoOrientationPortrait;
      //NSLog(@"UIDeviceOrientationPortrait--AVCaptureVideoOrientationPortrait");
      }
      }
      else{
      if (x >= 0){
      _deviceOrientation = UIDeviceOrientationLandscapeRight;
      _videoOrientation = AVCaptureVideoOrientationLandscapeRight;
      //NSLog(@"UIDeviceOrientationLandscapeRight--AVCaptureVideoOrientationLandscapeRight");
      }
      else{
      _deviceOrientation = UIDeviceOrientationLandscapeLeft;
      _videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
      // NSLog(@"UIDeviceOrientationLandscapeLeft--AVCaptureVideoOrientationLandscapeLeft");
      }
      }
      ;
      if (_delegate && [_delegate respondsToSelector:@selector(motionManagerDeviceOrientation:)]) {
      [_delegate motionManagerDeviceOrientation:_deviceOrientation];
      }
      }
      // 调整设备取向
    • (AVCaptureVideoOrientation)currentVideoOrientation{
      AVCaptureVideoOrientation orientation;
      switch ([SGMotionManager sharedManager].deviceOrientation) {
      case UIDeviceOrientationPortrait:
      orientation = AVCaptureVideoOrientationPortrait;
      break;
      case UIDeviceOrientationLandscapeRight:
      orientation = AVCaptureVideoOrientationLandscapeLeft;
      break;
      case UIDeviceOrientationPortraitUpsideDown:
      orientation = AVCaptureVideoOrientationPortraitUpsideDown;
      break;
      default:
      orientation = AVCaptureVideoOrientationLandscapeRight;
      break;
      }
      return orientation;
      }

    </pre>

    PS: 以上只是贴出了部分代码片段,完整代码请看 demo

    相关文章

      网友评论

      • 天上飞的狒狒:你好。你这个前置摄像头是镜像的,请问后期有解决吗?
        天上飞的狒狒:@低调的魅力 我添加了,似乎没起作用。
        低调的魅力:@天上飞的狒狒 这个是AVCaptureConnection对象的一个属性,可以自己修改哦:你全局搜索一下这个 self.videoConnection.videoMirrored就找到对应代码啦,你可以根据自己项目进行设置的
      • 烟雨莫:大神你好,进入拍摄界面有小段时间灰色,这个怎么解决
        低调的魅力:@烟雨莫 由于相机打开是阻塞线程,所以显示灰色只是个过渡,我把打开相机的代码放在了ViewDidAppear里面,其实微信也是这样做的,只不过它做了个模糊效果,如果不想有灰色过渡,你可以把代码提前到viewWillAppear里面,或者把那个灰色更改成黑色或者其他颜色
      • feng_dev:大神你的demo很赞,可就是不知道去哪里获取录好的视频,好尴尬。
        低调的魅力:@爱音乐的李小峰 录制结束后有一个 videoPath,就是视频在沙盒里的路径
      • feng_dev:大神很厉害。哈哈
      • 凡尘一笑:大兄弟 在不 我要拿到这个视频上传给服务器啊 怎么拿拍摄的这个视频啊
        低调的魅力:@凡尘一笑 拍摄完成后保存了在本地啊,拍摄成功的回调里面有 videoPath 啊,就是视频路径啊,转成 data 就可以上传啊
      • 忆白i:demo点不开
        低调的魅力:@iOS我称王 SGRecordManager.里有个MAX_TIME的宏定义,默认是10s自己改了就行啦
        忆白i:@低调的魅力 录制总时长需要改哪个参数
        低调的魅力:@iOS我称王 谢谢提醒,已经改了,现在可以啦

      本文标题:iOS小视频录制(类似微信)

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