iOS上的设备的采集篇

前言

如果只是单纯的想快速实现iOS端的采集,编码,推流,美颜,第三方框架或者商业SDK都能够快速实现,并且使用方便,易于上手,如果有什么不懂的地方问客服也基本上能够尽快的给你解决.由于公司需求的奇葩性我没办法使用市场上现有的轮子,只能自己去写.

LFLiveKit

这是我接触的第一个比较方便的框架吧,使用起来也很简单,这是框架在github上的地址:链接

使用步骤

  • 1 pod 'LFLiveKit',注意这个框架内部是包含了GPUimage的,所以重复添加可能会产生一些问题,请妥善解决.

  • 2 添加以下依赖库

使用范例

我个人感觉作者readme写的还是比较简单的,我这里会详细的写写,我自己写的demo地址:链接,如果文章描述的不清楚大家可以看看具体demo.

  • 1首先懒加载一个LFLiveSession,这很重要,音视频的一些参数都是在这里配置的.默认参数都是配置好了的,大家点进去看就好了.
-(LFLiveSession *)session{
        if (nil == _session) {
            /**
             默认音频质量 audio sample rate: 44MHz(默认44.1Hz iphoneg6以上48Hz), audio bitrate: 64Kbps
             分辨率: 540 *960 帧数:30 码率:800Kps
             方向竖屏
             */
            
            // 视频配置(质量 & 是否是横屏)可以点进去看
            _session = [[LFLiveSession alloc] initWithAudioConfiguration:[LFLiveAudioConfiguration defaultConfiguration] videoConfiguration:[LFLiveVideoConfiguration defaultConfigurationForQuality:LFLiveVideoQuality_Medium3 landscape:NO]];
            
            _session.delegate = self;
            _session.showDebugInfo = self;
            _session.preView = self.view;
        }
        return _session;
    }
  • 2 设置流信息

这里的url我是这么使用的,使用nginx服务器, Mac上是很好配置的,然后下载VLC,这里的URL就是rtmp://(你的ip):1935/rtmplive/room,将他配置到VLC应该代码完成后就可以正常推流显示了.

        LFLiveStreamInfo * sterm = [LFLiveStreamInfo new];
        
        sterm.url = @"rtmp://192.168.1.42:1935/rtmplive/room";
        
        [self.session startLive:sterm];
  • 3 美颜开关一键设置

具体的参数可以自己调,我个人觉得他的美颜效果还是不错的.

设置self.session.beautyFace为Yes或者No
  • 4前后镜头反转

       AVCaptureDevicePosition devicePosition = self.session.captureDevicePosition;
      
      self.session.captureDevicePosition = (devicePosition == AVCaptureDevicePositionBack) ? AVCaptureDevicePositionFront : AVCaptureDevicePositionBack; 
    
  • 5闪光灯

通过AVCaptureTorchMode这个类对其进行控制.

  • 6镜面翻转

    [self.session setMirror:YES];
    
  • 7请求授权

iOS10中应该是要在plist文件中再加入一些键值对的.

- (void)requestAccessForVideo {
        @weakify(self);
        AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
        switch (status) {
            case AVAuthorizationStatusNotDetermined: {
                // 许可对话没有出现,发起授权许可
                [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
                    if (granted) {
                        dispatch_async(dispatch_get_main_queue(), ^{
                            @strongify(self);
                            [self.session setRunning:YES];
                        });
                    }
                }];
                break;
            }
            case AVAuthorizationStatusAuthorized: {
                // 已经开启授权,可继续
                dispatch_async(dispatch_get_main_queue(), ^{
                    @strongify(self);
                    [self.session setRunning:YES];
                });
                break;
            }
            case AVAuthorizationStatusDenied:
            case AVAuthorizationStatusRestricted:
                // 用户明确地拒绝授权,或者相机设备无法访问
                
                break;
            default:
                break;
        }
    }

    - (void)requestAccessForAudio {
        AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
        switch (status) {
            case AVAuthorizationStatusNotDetermined: {
                [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
                }];
                break;
            }
            case AVAuthorizationStatusAuthorized: {
                break;
            }
            case AVAuthorizationStatusDenied:
            case AVAuthorizationStatusRestricted:
                break;
            default:
                break;
        }
    }
  • 8 监听推送状态的代理方法

    -(void)liveSession:(LFLiveSession *)session liveStateDidChange:(LFLiveState)state{
          self.liveStateLabel.textColor = [UIColor blackColor];
          switch (state) {
              case LFLiveReady:
                  NSLog(@"未连接");
                  self.liveStateLabel.text = @"未连接";
                  break;
              case LFLivePending:
                  NSLog(@"连接中");
                  [FYProgressHUD showWithMessage:@"正在连接..."];
                  self.liveStateLabel.text = @"正在连接...";
                  break;
              case LFLiveStart:
                  NSLog(@"已连接");
                  [FYProgressHUD showSuccess:@"连接成功"];
                  self.liveStateLabel.text = @"正在直播";
                  break;
              case LFLiveError:
                  NSLog(@"连接错误");
                  [FYProgressHUD showError:@"连接错误"];
                  self.liveStateLabel.text = @"未连接";
                  break;
              case LFLiveStop:
                  self.liveStateLabel.text = @"未连接";
                  NSLog(@"未连接");
                  break;
              default:
                  break;
          }
      }
    

至此如果你只是想快速实现推流那么就已经结束了,可以不必看下文了.

使用系统方法获取数据(不带美颜效果)

一般情况下我们是不考虑这种方法的,因为当今直播市场非常火爆,如果你直接做一个不带美颜功能的,我想你这app估计很多主播都不好意思用吧,这里只是以介绍为主.

  • 1 创建音视频输入

    /**
       创建音视频输入
       */
      -(void)createCaptureDevice{
          
          //创建摄像头
          NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
          
          //初始化摄像头
          self.frontCamera = [AVCaptureDeviceInput deviceInputWithDevice:videoDevices.lastObject error:nil];
          self.backCamera = [AVCaptureDeviceInput deviceInputWithDevice:videoDevices.firstObject error:nil];
          
          //麦克风
          AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
          self.audioInputDevice = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:nil];
          
          //设置当前视频输入设备
          self.videoInputDevice = self.backCamera;
      }
    
  • 2 创建输出

设置代理的时候注意遵守着两个协议

AVCaptureVideoDataOutputSampleBufferDelegate
AVCaptureAudioDataOutputSampleBufferDelegate

这两个协议的代理方法是我们后面获取SampleBuffer的途径

    /**
     创建输出
     */
    -(void)createOutput{
        
        //创建队列
        dispatch_queue_t queue = dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL);
        
        //创建视频输出数据
        self.videoDataOutput = [[AVCaptureVideoDataOutput alloc]init];
        
        //设置代理和队列
        [self.videoDataOutput setSampleBufferDelegate:self queue:queue];
        
        //抛弃过期帧,保证实时性
        self.videoDataOutput.alwaysDiscardsLateVideoFrames = YES;
        
        //设置参数
        NSDictionary * settings = @{(__bridge id)kCVPixelBufferPixelFormatTypeKey:@(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)};
        self.videoDataOutput.videoSettings = settings;
        
        //创建音频输出数据
        self.audioDataOutput = [[AVCaptureAudioDataOutput alloc]init];
        [self.audioDataOutput setSampleBufferDelegate:self queue:queue];
    }
  • 3 创建会话

    /**
       创建会话
       */
      -(void)createCaptureSession{
          
          self.captureSession = [[AVCaptureSession alloc]init];
          
          [self.captureSession beginConfiguration];
          
          //添加视频输入
          if ([self.captureSession canAddInput:self.videoInputDevice]) {
              [self.captureSession addInput:self.videoInputDevice];
          }
          
          //添加音频输入
          if ([self.captureSession canAddInput:self.audioInputDevice]) {
              [self.captureSession addInput:self.audioInputDevice];
          }
          
          //添加视频输出
          if([self.captureSession canAddOutput:self.videoDataOutput]){
              [self.captureSession addOutput:self.videoDataOutput];
      //        [self setVideoOutConfig];
          }
          
          //添加音频输出
          if([self.captureSession canAddOutput:self.audioDataOutput]){
              [self.captureSession addOutput:self.audioDataOutput];
          }
          
          //设置预览分辨率
          //这个分辨率有一个值得注意的点:
          //iphone4录制视频时 前置摄像头只能支持 480*640 后置摄像头不支持 540*960 但是支持 720*1280
          //诸如此类的限制,所以需要写一些对分辨率进行管理的代码。
          //目前的处理是,对于不支持的分辨率会抛出一个异常
          //但是这样做是不够、不完整的,最好的方案是,根据设备,提供不同的分辨率。
          //如果必须要用一个不支持的分辨率,那么需要根据需求对数据和预览进行裁剪,缩放。
          //设置采集质量
      //    if (![self.captureSession canSetSessionPreset:self.captureSessionPreset]) {
      //        @throw [NSException exceptionWithName:@"Not supported captureSessionPreset" reason:[NSString stringWithFormat:@"captureSessionPreset is [%@]", self.captureSessionPreset] userInfo:nil];
      //    }
          
          //设置采集质量
          self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;//默认就为高
          
          //提交配置变更
          [self.captureSession commitConfiguration];
    
      }
    
  • 4 创建预览

    /**
       创建预览
       */
      -(void)createPreviewLayer{
          
          self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
          
          self.previewLayer.frame = self.view.frame;
          
          //保留纵横比
          self.previewLayer.videoGravity = AVVideoScalingModeResizeAspectFill;
          
          [self.view.layer addSublayer:self.previewLayer];
          
      }
    
  • 5 退出销毁的时候

    if (self.captureSession) {
      [self.captureSession removeInput:self.audioInputDevice];
      [self.captureSession removeInput:self.videoInputDevice];
      [self.captureSession removeOutput:self.self.videoDataOutput];
      [self.captureSession removeOutput:self.self.audioDataOutput];
    }
    
    [self.captureSession stopRunning];
    self.captureSession = nil;
    
  • 6 获取数据的地方

在这个代理方法里是获取音视频数据的地方,这里是我们对采集到的原始数据进行编码的地方,后面会讲到如何去编码,这个方法是能够区分音频和视频信息的.

-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;

使用GPUimage

-(void)onInit{
        //摄像头初始化
        // AWGPUImageVideoCamera 继承自 GPUImageVideoCamera。继承是为了获取音频数据,原代码中,默认情况下音频数据发送给了 audioEncodingTarget。
        // 这个东西一看类型是GPUImageMovieWriter,应该是文件写入功能。果断覆盖掉processAudioSampleBuffer方法,拿到音频数据后自己处理。
        // 音频就这样可以了,GPUImage主要工作还是在视频处理这里。
        // 设置预览分辨率 self.captureSessionPreset是根据AWVideoConfig的设置,获取的分辨率。设置前置、后置摄像头。
        _videoCamera = [[AWGPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPresetHigh cameraPosition:AVCaptureDevicePositionFront];
        
        //开启捕获声音
        [_videoCamera addAudioInputsAndOutputs];
        
        //设置输出图像方向,可用于横屏推流。
        _videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
        
        //镜像策略,这里这样设置是最自然的。跟系统相机默认一样。
        _videoCamera.horizontallyMirrorRearFacingCamera = NO;
        _videoCamera.horizontallyMirrorFrontFacingCamera = YES;
        
        //设置预览view
        _gpuImageView = [[GPUImageView alloc] initWithFrame:self.view.bounds];
        [self.view addSubview:_gpuImageView];
        
        //初始化美颜滤镜
        _beautifyFilter = [[GPUImageBeautifyFilter alloc] init];
        
        //相机获取视频数据输出至美颜滤镜
        [_videoCamera addTarget:_beautifyFilter];
        
        //美颜后输出至预览
        [_beautifyFilter addTarget:_gpuImageView];
        
        // 到这里我们已经能够打开相机并预览了。
        // 因为要推流,除了预览之外,我们还要截取到视频数据。这就需要使用GPUImage中的GPUImageRawDataOutput,它能将美颜后的数据输出,便于我们处理后发送出去。
        // AWGPUImageAVCaptureDataHandler继承自GPUImageRawDataOutput,从 newFrameReadyAtTime 方法中就可以获取到美颜后输出的数据。
        // 输出的图片格式为BGRA。
        _dataHandler = [[AWGPUImageAVCaptureDataHandler alloc]initWithImageSize:CGSizeMake(self.view.frame.size.width, self.view.frame.size.height) resultsInBGRAFormat:YES ];
        [_beautifyFilter addTarget:_dataHandler];
        
        // 令AWGPUImageAVCaptureDataHandler实现AWGPUImageVideoCameraDelegate协议,并且让camera的awAudioDelegate指向_dataHandler对象。
        // 将音频数据转到_dataHandler中处理。然后音视频数据就可以都在_dataHandler中处理了。
        _videoCamera.awAudioDelegate = _dataHandler;
        
        //开始捕获视频
        [self.videoCamera startCameraCapture];
        
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容