ios直播技术(1)-- 视频采集

4 篇文章 0 订阅
3 篇文章 0 订阅

自己封装了一个视频采集类。

1.支持动态修改前后置摄像头。

2.支持分辨率、帧率修改,当设置不支持的分辨率时自动匹配最相近的分辨率。

3.视频预览功能




VideoCapture.h

//
//  VideoCapture.h
//  VideoCapture
//
//  Created by whw on 16-8-27.
//  Copyright (c) 2016年 All rights reserved.
//

#import <AVFoundation/AVFoundation.h>
#import "AVDefine.h"

class  NativeVideoFrame;
struct VideoCapability;

@protocol CaptureDelegate <Reporter>
- (void)rawFrame:(NativeVideoFrame*)frame Capability:(struct VideoCapability*)capability;
@end

@interface VideoCapture : NSObject
- (id)initWithDelegate:(id<CaptureDelegate>)delegate;
- (BOOL)setCaptureDeviceByPosition:(BOOL)front;
- (BOOL)setCaptureCapability:(struct VideoCapability*)capability;
- (BOOL)startCapture;
- (BOOL)stopCapture;
- (AVCaptureVideoPreviewLayer *)previewLayer;
- (void)takePicture:(int)size Complete:(void(^)(UIImage*, NSError*))complete;
@end

VideoCapture.mm


//
//  VideoCapture.m
//  VideoCapture
//
//  Created by whw on 16-8-27.
//  Copyright (c) 2016年 All rights reserved.
//
#import <VideoCapture.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <Utils.h>

#include "VideoFrame.h"
#import "VideoDefines.h"

static dispatch_queue_t captureQueue = nil;
static const char *captureQueueSpecific = "com.video.capturequeue";

@interface VideoCapture() <AVCaptureVideoDataOutputSampleBufferDelegate>
{
    __weak id<CaptureDelegate> _owner;
    VideoCapability _capability;
    AVCaptureSession* _captureSession;
    Rotation _frameRotation;
    BOOL _orientationHasChanged;
    AVCaptureConnection* _connection;
    BOOL _captureChanging;  // Guarded by _captureChangingCondition.
    NSCondition* _captureChangingCondition;
    NativeVideoFrame _videoFrame;
}
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *prevLayer;
@property (nonatomic, strong) NSDictionary *resolutionDic;
@end
@implementation VideoCapture
- (dispatch_queue_t)captureQueue
{
    if (captureQueue == NULL)
    {
        captureQueue = dispatch_queue_create(captureQueueSpecific, 0);
        
        dispatch_queue_set_specific(captureQueue, captureQueueSpecific, (void *)captureQueueSpecific, NULL);
    }
    return captureQueue;
}
- (bool)isCurrentQueueCaptureQueue
{
    return dispatch_get_specific(captureQueueSpecific) != NULL;
}
- (void)dispatchOnCaptureQueue:(dispatch_block_t)block synchronous:(bool)synchronous
{
    if ([self isCurrentQueueCaptureQueue])
    {
        @autoreleasepool
        {
            block();
        }
    }
    else
    {
        if (synchronous)
        {
            dispatch_sync([self captureQueue], ^
                          {
                              @autoreleasepool
                              {
                                  block();
                              }
                          });
        }
        else
        {
            dispatch_async([self captureQueue], ^
                           {
                               @autoreleasepool
                               {
                                   block();
                               }
                           });
        }
    }
}
- (id)initWithDelegate:(id<CaptureDelegate>)owner
{
    if (self == [super init]) {
        _owner = owner;
        _captureSession = [[AVCaptureSession alloc] init];
#if defined(__IPHONE_7_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_7_0
        NSString* version = [[UIDevice currentDevice] systemVersion];
        if ([version integerValue] >= 7) {
            _captureSession.usesApplicationAudioSession = NO;
            _captureSession.automaticallyConfiguresApplicationAudioSession = NO;
        }
#endif
        _captureChanging = NO;
        _captureChangingCondition = [[NSCondition alloc] init];
        
        if (!_captureSession || !_captureChangingCondition) {
            return nil;
        }
        
        // create and configure a new output (using callbacks)
        AVCaptureVideoDataOutput* captureOutput = [[AVCaptureVideoDataOutput alloc] init];
        NSString* key = (NSString*)kCVPixelBufferPixelFormatTypeKey;
        
        NSNumber* val = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange];
        NSDictionary* videoSettings = [NSDictionary dictionaryWithObject:val forKey:key];
        [captureOutput setVideoSettings:videoSettings];
        [captureOutput setAlwaysDiscardsLateVideoFrames:YES];
        // add new output
        if ([_captureSession canAddOutput:captureOutput]) {
            [_captureSession addOutput:captureOutput];
        } else {
            //error
        }
        
        if (!_prevLayer)
        {
            _prevLayer = [AVCaptureVideoPreviewLayer layerWithSession:_captureSession];
            _prevLayer.videoGravity = AVLayerVideoGravityResizeAspect;
        }
        [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
        NSNotificationCenter* notify = [NSNotificationCenter defaultCenter];
        [notify addObserver:self
                   selector:@selector(onVideoError:)
                       name:AVCaptureSessionRuntimeErrorNotification
                     object:_captureSession];
        [notify addObserver:self
                   selector:@selector(deviceOrientationDidChange:)
                       name:UIDeviceOrientationDidChangeNotification
                     object:nil];
    }
    
    return self;
}

- (void)directOutputToSelf {
    [[self currentOutput] setSampleBufferDelegate:self queue:[self captureQueue]];
}

- (void)directOutputToNil {
    [[self currentOutput] setSampleBufferDelegate:nil queue:NULL];
}
- (void)deviceOrientationDidChange:(NSNotification*)notification {
    _orientationHasChanged = YES;
    [self setRelativeVideoOrientation];
}
- (void)setRelativeVideoOrientation {
    if (!_connection.supportsVideoOrientation) {
        return;
    }
    
    switch ([UIDevice currentDevice].orientation) {
        case UIDeviceOrientationPortrait:
            _connection.videoOrientation = AVCaptureVideoOrientationPortrait;
            break;
        case UIDeviceOrientationPortraitUpsideDown:
            _connection.videoOrientation = AVCaptureVideoOrientationPortraitUpsideDown;
            break;
        case UIDeviceOrientationLandscapeLeft:
            _connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
            break;
        case UIDeviceOrientationLandscapeRight:
            _connection.videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
            break;
        case UIDeviceOrientationFaceUp:
        case UIDeviceOrientationFaceDown:
        case UIDeviceOrientationUnknown:
            if (!_orientationHasChanged) {
                _connection.videoOrientation = AVCaptureVideoOrientationPortrait;
            }
            break;
    }
    _prevLayer.connection.videoOrientation = _connection.videoOrientation;
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (BOOL)setCaptureDeviceByPosition:(BOOL)front
{
    [self waitForCaptureChangeToFinish];
    // check to see if the camera is already set
    
    AVCaptureDevicePosition desiredPosition = front?AVCaptureDevicePositionFront:AVCaptureDevicePositionBack;

    if (_captureSession)
    {
        NSArray* currentInputs = [NSArray arrayWithArray:[_captureSession inputs]];
        if ([currentInputs count] > 0)
        {
            AVCaptureDeviceInput* currentInput = [currentInputs objectAtIndex:0];
            if ([currentInput.device position] == desiredPosition)
            {
                return YES;
            }
        }
    }
    
    return [self changeCaptureInputByPosition:desiredPosition];
}
- (BOOL)setCaptureCapability:(struct VideoCapability*)capability
{
    if ((*capability) == _capability)
        return YES;
    BOOL restart = [_captureSession isRunning];
    
    if (restart)
    {
        [self stopCapture];
    }
    
    _capability = *capability;
    
    if (restart)
    {
        [self startCapture];
    }
    return YES;
}
- (BOOL)startCapture
{
    [self waitForCaptureChangeToFinish];
    if (!_captureSession) {
        return NO;
    }
    
    AVCaptureVideoDataOutput* currentOutput = [self currentOutput];
    if (!currentOutput)
        return NO;
    
    [self directOutputToSelf];
    
    _orientationHasChanged = NO;
    _captureChanging = YES;
    dispatch_async([self captureQueue], ^(void) { [self startCaptureInBackgroundWithOutput:currentOutput]; });
    return YES;
}

- (AVCaptureVideoDataOutput*)currentOutput {
    return [[_captureSession outputs] firstObject];
}

- (void)startCaptureInBackgroundWithOutput: (AVCaptureVideoDataOutput*)currentOutput {
    // begin configuration for the AVCaptureSession
    [_captureSession beginConfiguration];
    
    // take care of capture framerate now
    NSArray* sessionInputs = _captureSession.inputs;
    AVCaptureDeviceInput* deviceInput = [sessionInputs count] > 0 ? sessionInputs[0] : nil;
    AVCaptureDevice* inputDevice = deviceInput.device;
    if (inputDevice) {
        NSString* captureQuality = [self presetFromResolution:inputDevice];
        // picture resolution
        [_captureSession setSessionPreset:captureQuality];
        
        CMTime tm = CMTimeMake(1, _capability.fps);
        [inputDevice lockForConfiguration:nil];
        [inputDevice setActiveVideoMaxFrameDuration:tm];
        [inputDevice setActiveVideoMinFrameDuration:tm];
        [inputDevice unlockForConfiguration];
    }
    
    _connection = [currentOutput connectionWithMediaType:AVMediaTypeVideo];
    [self setRelativeVideoOrientation];
    
    // finished configuring, commit settings to AVCaptureSession.
    [_captureSession commitConfiguration];
    
    [_captureSession startRunning];
    [self signalCaptureChangeEnd];
}

- (void)onVideoError:(NSNotification*)notification {
    AVLogError(@"onVideoError: %@", notification);
}

- (BOOL)stopCapture {
    [self waitForCaptureChangeToFinish];
    [self directOutputToNil];
    
    if (!_captureSession) {
        return NO;
    }
    _orientationHasChanged = NO;
    _captureChanging = YES;
    dispatch_async([self captureQueue], ^(void) { [self stopCaptureInBackground]; });
    return YES;
}

- (void)stopCaptureInBackground {
    [_captureSession stopRunning];
    [self signalCaptureChangeEnd];
}
+ (int)captureDeviceCount {
    return (int)[[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count];
}

+ (AVCaptureDevice*)captureDeviceForPosition:(AVCaptureDevicePosition)positon
{
    for (AVCaptureDevice* device in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
        if (positon == device.position) {
            return device;
        }
    }
    
    return nil;
}
- (BOOL)changeCaptureInputByPosition:(AVCaptureDevicePosition)positon {
    BOOL restart = [_captureSession isRunning];
    if (restart)
    {
        [self stopCapture];
    }
    [self waitForCaptureChangeToFinish];
    NSArray* currentInputs = [_captureSession inputs];
    // remove current input
    if ([currentInputs count] > 0) {
        AVCaptureInput* currentInput = (AVCaptureInput*)[currentInputs objectAtIndex:0];
        
        [_captureSession removeInput:currentInput];
    }
    
    // Look for input device with the name requested (as our input param)
    // get list of available capture devices
    int captureDeviceCount = [VideoCapture captureDeviceCount];
    if (captureDeviceCount <= 0) {
        return NO;
    }
    
    AVCaptureDevice* captureDevice = [VideoCapture captureDeviceForPosition:positon];
    
    if (!captureDevice) {
        return NO;
    }
    
    // now create capture session input out of AVCaptureDevice
    NSError* deviceError = nil;
    AVCaptureDeviceInput* newCaptureInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice
                                          error:&deviceError];
    
    if (!newCaptureInput) {
        const char* errorMessage = [[deviceError localizedDescription] UTF8String];
        UNUSED(errorMessage);
        //todo tyy  开始设备失败
        [self onError:Session_Errot_OpenCamera_Failed];
        return NO;
    }
    
    // try to add our new capture device to the capture session
    [_captureSession beginConfiguration];
    
    BOOL addedCaptureInput = NO;
    if ([_captureSession canAddInput:newCaptureInput]) {
        [_captureSession addInput:newCaptureInput];
        addedCaptureInput = YES;
    } else {
        addedCaptureInput = NO;
    }
    
    [_captureSession commitConfiguration];
    
    if (restart)
    {
        [self startCapture];
    }
    return addedCaptureInput;
}

- (void)captureOutput:(AVCaptureOutput*)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection*)connection {
    CVImageBufferRef videoFrame = CMSampleBufferGetImageBuffer(sampleBuffer);
    
    _videoFrame.CreateFrame(videoFrame);
    
    VideoCapability tempCaptureCapability;
    tempCaptureCapability.width = _videoFrame.width();
    tempCaptureCapability.height = _videoFrame.height();
    tempCaptureCapability.fps = _capability.fps;
    
    if (_owner)
    {
        [_owner rawFrame:&_videoFrame Capability:&tempCaptureCapability];
    }
    _videoFrame.Reset();
}

- (void)signalCaptureChangeEnd {
    [_captureChangingCondition lock];
    _captureChanging = NO;
    [_captureChangingCondition signal];
    [_captureChangingCondition unlock];
}

- (void)waitForCaptureChangeToFinish {
    [_captureChangingCondition lock];
    while (_captureChanging) {
        [_captureChangingCondition wait];
    }
    [_captureChangingCondition unlock];
}
- (AVCaptureVideoPreviewLayer *)previewLayer
{
    return _prevLayer;
}
- (void)takePicture:(int)size Complete:(void(^)(UIImage*, NSError*))complete
{
    /*[self dispatchOnCaptureQueue:^{
        do {
            if (!self.imageOutput)
                break;
            
            _takingPicture = YES;
            AVCaptureConnection *videoConnection = nil;
            for (AVCaptureConnection *connection in self.imageOutput.connections) {
                for (AVCaptureInputPort *port in [connection inputPorts]) {
                    if ([[port mediaType] isEqual:AVMediaTypeVideo] ) {
                        videoConnection = connection;
                        break;
                    }
                }
                if (videoConnection) {
                    break;
                }
            }
            
            // 拍照
            NSLog(@"Picture taking");
            [self.imageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error)
             {
                 UIImage *image = nil;
                 if (CMSampleBufferIsValid(imageSampleBuffer))
                 {
                     
                     // 获取图片数据
                     NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
                     image = [[UIImage alloc] initWithData:imageData];
                 }
                 
                 dispatch_async([self captureQueue], ^{
                     
                     _takingPicture = NO;
                     
                     NSLog(@"Picture taken");
                     if (complete)
                     {
                         dispatch_async(dispatch_get_main_queue(), ^{
                             complete(image, error);
                         });
                     }
                 });
                 
             }];
            
            return;
        } while (0);
        
        if (complete)
        {
            dispatch_async(dispatch_get_main_queue(), ^{
                complete(nil, nil);
            });
        }
        
    } synchronous:false];*/
}
+ (NSDictionary *)getResolutions
{
    NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
    
    for (AVCaptureDevice * videoDevice in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo])
    {
        if ([videoDevice supportsAVCaptureSessionPreset:AVCaptureSessionPreset1920x1080])
        {
            [dic setObject:AVCaptureSessionPreset1920x1080 forKey:@"1920*1080"];
        }
        if ([videoDevice supportsAVCaptureSessionPreset:AVCaptureSessionPreset1280x720])
        {
            [dic setObject:AVCaptureSessionPreset1280x720 forKey:@"1280*720"];
        }
        if ([videoDevice supportsAVCaptureSessionPreset:AVCaptureSessionPreset640x480])
        {
            [dic setObject:AVCaptureSessionPreset640x480 forKey:@"640*480"];
        }
        if ([videoDevice supportsAVCaptureSessionPreset:AVCaptureSessionPreset352x288])
        {
            [dic setObject:AVCaptureSessionPreset352x288 forKey:@"352*288"];
        }
        if ([videoDevice supportsAVCaptureSessionPreset:AVCaptureSessionPresetLow])
        {
            [dic setObject:AVCaptureSessionPresetLow forKey:@"192*144"];
        }
    }
    
    return dic;
}

+(NSArray*)sortedResolutionArray:(NSDictionary*)dic
{
    NSArray* arr = [[dic allKeys] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2){
        
        NSString* str1 = obj1;
        NSArray *listItems1 = [str1 componentsSeparatedByString:@"*"];
        int w1 = [[listItems1 objectAtIndex:0] intValue];
        int h1 = [[listItems1 objectAtIndex:1] intValue];
        
        NSString* str2 = obj2;
        NSArray *listItems2 = [str2 componentsSeparatedByString:@"*"];
        int w2 = [[listItems2 objectAtIndex:0] intValue];
        int h2 = [[listItems2 objectAtIndex:1] intValue];
        
        if (w1 > w2)
        {
            return NSOrderedDescending;
        }
        
        if (w1 < w2)
        {
            return NSOrderedAscending;
        }
        
        if (h1 > h2)
        {
            return NSOrderedDescending;
        }
        
        if (h1 < h2)
        {
            return NSOrderedAscending;
        }
        
        return NSOrderedSame;
    }];
    
    return arr;
}

- (NSString *)presetFromResolution:(AVCaptureDevice*)device
{
    if (!self.resolutionDic)
        self.resolutionDic = [VideoCapture getResolutions];
    NSString* sessionPreset = [NSString stringWithFormat:@"%d*%d",_capability.width,_capability.height];
    NSString* DesiredPreset = [self.resolutionDic objectForKey:sessionPreset];
    if(DesiredPreset && [device supportsAVCaptureSessionPreset:DesiredPreset])
    {
        //这里表示 这个设备支持此分辨率
        return DesiredPreset;
    }
    //todo tyy 上面没有return 说明不支持此分辨率 需要给上层提示
    [self onWarning:Session_Warning_VideoProfile_NotSupported];
    
    NSArray* arr = [VideoCapture sortedResolutionArray:self.resolutionDic];
    for (int i = 0; i < arr.count; ++i)
    {
        if([arr objectAtIndex:i] == sessionPreset)
        {
            for(int j = 1;(i + j < arr.count || i - j >= 0); ++j)
            {
                NSString* preset = nil;
                if(i - j >= 0)
                {
                    preset = [arr objectAtIndex:i - j];
                    if([device supportsAVCaptureSessionPreset:[self.resolutionDic objectForKey:preset]])
                    {
                        return [self.resolutionDic objectForKey:preset];
                    }
                }
                if(i+j < arr.count)
                {
                    preset = [arr objectAtIndex:i + j];
                    if([device supportsAVCaptureSessionPreset:[self.resolutionDic objectForKey:preset]])
                    {
                        return [self.resolutionDic objectForKey:preset];
                    }
                }
            }
            
            break;
        }
    }
    
    return AVCaptureSessionPresetLow;
}

#pragma mark - Reporter

- (void)onWarning:(AVSessionWaringCode)warning
{
    if (_owner && [_owner respondsToSelector:@selector(onWarning:)]) {
        [_owner onWarning:warning];
    }
}
- (void)onError:(AVSessionErrorCode)error
{
    if (_owner && [_owner respondsToSelector:@selector(onError:)]) {
        [_owner onError:error];
    }
}
@end


转载请注明原地址,  谢谢!

源码地址:https://github.com/haowei8196/VideoEngineMgr

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值