美文网首页
媒体捕获

媒体捕获

作者: 低吟浅唱1990 | 来源:发表于2019-03-21 23:26 被阅读0次
41553175912_.pic.jpg

AV Foundation捕捉会话的核心是AVCaptureSession。一个捕捉会话相当于一个虚拟的“插线板”,用于连接输入和输出的资源。捕捉会话管理从设备得到的数据流,比如摄像头和麦克风设备,输出一个或多个目的地。

捕捉设备

AVCaptureDevice是诸如摄像头和麦克风等物理设备定义了一个接口。AVCaptureDevice针对物理硬件设备定义了大量的控制方法,比如摄像头的对焦、曝光、白平衡和闪光灯等。

捕捉设备的输入

AVCaptureDevice需要包装成AVCaptureDeviceInput实例来与AVCaptureSession进行对接

捕捉设备的输出

上图AVCaptureSession的右边即设备的输出。

捕捉预览

与视频播放类似预览层也是一个有Core Animation的CALayer的子类满足的----AVCaptureVideoPreviewLayer.

代码部分

创建捕捉控制器

THCameraController.h

//引入AVFoundation头文件
#import <AVFoundation/AVFoundation.h>

extern NSString *const THThumbnailCreatedNotification;

@protocol THCameraControllerDelegate <NSObject>                             // 1 定义了当有错误发生时,委托代理的方法
- (void)deviceConfigurationFailedWithError:(NSError *)error;
- (void)mediaCaptureFailedWithError:(NSError *)error;
- (void)assetLibraryWriteFailedWithError:(NSError *)error;
@end

@interface THCameraController : NSObject

@property (weak, nonatomic) id<THCameraControllerDelegate> delegate;
@property (nonatomic, strong, readonly) AVCaptureSession *captureSession;

// 配置会话                                                 // 2 配置会话 传入一个error对象的指针的指针,一旦出错外界可以直接获取错误信息
- (BOOL)setupSession:(NSError **)error;
- (void)startSession;
- (void)stopSession;

// 摄像头相关                                                // 3
- (BOOL)switchCameras;      //切换摄像头
- (BOOL)canSwitchCameras;  // 能否切换摄像头
@property (nonatomic, readonly) NSUInteger cameraCount;  //能用的摄像头的个数
@property (nonatomic, readonly) BOOL cameraHasTorch;  //是否支持手电筒模式
@property (nonatomic, readonly) BOOL cameraHasFlash; //是否支持闪光灯模式
@property (nonatomic, readonly) BOOL cameraSupportsTapToFocus;//是否支持聚焦
@property (nonatomic, readonly) BOOL cameraSupportsTapToExpose; // 是否支持曝光
@property (nonatomic) AVCaptureTorchMode torchMode; //手电筒模式
@property (nonatomic) AVCaptureFlashMode flashMode; //闪光灯模式
/** Media Capture Methods **/                            // 5 捕捉图片和视频
- (void)captureStillImage;
// Video Recording
- (void)startRecording;
- (void)stopRecording;
- (BOOL)isRecording;
- (CMTime)recordedDuration;

@end

@interface THCameraController () <AVCaptureFileOutputRecordingDelegate>
@property (strong, nonatomic) AVCaptureSession *captureSession;
@property (weak, nonatomic) AVCaptureDeviceInput *activeVideoInput;
@property (strong, nonatomic) AVCaptureStillImageOutput *imageOutput;
@property (strong, nonatomic) AVCaptureMovieFileOutput *movieOutput;
@property (strong, nonatomic) NSURL *outputURL;
@end

@implementation THCameraController
- (BOOL)setupSession:(NSError **)error{

    self.captureSession = [[AVCaptureSession alloc] init];                  // 1创建会话
    //设置预设值
    self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;
    // Set up default camera device
    AVCaptureDevice *videoDevice =                                          // 2得到一个默认视频捕捉设备的的实例
        [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    AVCaptureDeviceInput *videoInput =                                      // 3 把AVCaptureDevice包装成AVCaptureDeviceInput对象添加到会话中
        [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
    if (videoInput) {
        if ([self.captureSession canAddInput:videoInput]) {          // 4是否可以被添加到会话中,如果可以则添加
            [self.captureSession addInput:videoInput];
            self.activeVideoInput = videoInput;
        }
    } else {
        return NO;
    }
    AVCaptureDevice *audioDevice =                                    // 5 获取音频设备的指针
        [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];

    AVCaptureDeviceInput *audioInput =                          // 6 包装audioDevice的输入
        [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:error];
    if (audioInput) {
        if ([self.captureSession canAddInput:audioInput]) {     // 7是否可以被添加到会话中,如果可以则添加
            [self.captureSession addInput:audioInput];
        }
    } else {
        return NO;
    }
    self.imageOutput = [[AVCaptureStillImageOutput alloc] init];            // 8 创建一个AVCaptureStillImageOutput实例,用于从摄像头中捕捉静态图片
    self.imageOutput.outputSettings = @{AVVideoCodecKey : AVVideoCodecJPEG};

    if ([self.captureSession canAddOutput:self.imageOutput]) {
        [self.captureSession addOutput:self.imageOutput];
    }
    self.movieOutput = [[AVCaptureMovieFileOutput alloc] init];             // 9 创建一个AVCaptureMovieFileOutput实例用于输出视频文件。

    if ([self.captureSession canAddOutput:self.movieOutput]) { //同样方式添加输出
        [self.captureSession addOutput:self.movieOutput];
    }

    return YES;

}

- (void)startSession {
    if (![self.captureSession isRunning]) {                                 // 10 开始会话 异步调用不会阻塞主线程
        dispatch_async([self globalQueue], ^{
            [self.captureSession startRunning];
        });
    }
}

- (void)stopSession {
    if ([self.captureSession isRunning]) {                                  // 11 停止会话
        dispatch_async([self globalQueue], ^{
            [self.captureSession stopRunning];
        });
    }
}
//返回一个队列
- (dispatch_queue_t)globalQueue {
    return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
}

// 摄像头
- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position { // 12返回指定位置的摄像头
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *device in devices) {                              // 遍历可用的视频设备并返回对对应的设备
        if (device.position == position) {
            return device;
        }
    }
    return nil;
}

- (AVCaptureDevice *)activeCamera {                                         // 13 返回当前激活的视频设备
    return self.activeVideoInput.device;
}

- (AVCaptureDevice *)inactiveCamera {                                       // 14 返回当前未激活的设备
    AVCaptureDevice *device = nil;
    if (self.cameraCount > 1) {
        if ([self activeCamera].position == AVCaptureDevicePositionBack) {  // 
            device = [self cameraWithPosition:AVCaptureDevicePositionFront];
        } else {
            device = [self cameraWithPosition:AVCaptureDevicePositionBack];
        }
    }
    return device;
}

- (BOOL)canSwitchCameras {                                                  // 15 返回可用视频捕捉设备
    return self.cameraCount > 1;
}

- (NSUInteger)cameraCount {                                                 // 
    return [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count];
}

//切换摄像头
- (BOOL)switchCameras {

    if (![self canSwitchCameras]) {                                         // 16 是否可以切换摄像头(摄像头至少多余1个)
        return NO;
    }

    NSError *error;
    AVCaptureDevice *videoDevice = [self inactiveCamera];                   // 17 获取为激活摄像头指针,并包装一个AVCaptureDeviceInput输入

    AVCaptureDeviceInput *videoInput =
    [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];

    if (videoInput) {
        [self.captureSession beginConfiguration];                           // 18 开始设置会话

        [self.captureSession removeInput:self.activeVideoInput];            // 19 移除正在使用的激活摄像头

        if ([self.captureSession canAddInput:videoInput]) {                 // 20 添加17不创建的新的AVCaptureDeviceInput输入
            [self.captureSession addInput:videoInput];
            self.activeVideoInput = videoInput;
        } else {
            [self.captureSession addInput:self.activeVideoInput];
        }

        [self.captureSession commitConfiguration];                          // 21 提交设置  18-21步固定步骤, 先锁住 然后切换 自后提交配置

    } else {
        [self.delegate deviceConfigurationFailedWithError:error];           // 22 错误回调
        return NO;
    }

    return YES;
}

// 闪光 和 手电筒模式

- (BOOL)cameraHasFlash {
    return [[self activeCamera] hasFlash];
}

- (AVCaptureFlashMode)flashMode {
    return [[self activeCamera] flashMode];
}

// 设置闪光灯模式
- (void)setFlashMode:(AVCaptureFlashMode)flashMode {

    AVCaptureDevice *device = [self activeCamera];

    if (device.flashMode != flashMode &&
        [device isFlashModeSupported:flashMode]) {

        NSError *error;
        if ([device lockForConfiguration:&error]) {  // 锁定设置
            device.flashMode = flashMode;            // 解锁设置
            [device unlockForConfiguration];         // 解锁设置
        } else {
            [self.delegate deviceConfigurationFailedWithError:error];
        }
    }
}

- (BOOL)cameraHasTorch {
    return [[self activeCamera] hasTorch];
}

- (AVCaptureTorchMode)torchMode {
    return [[self activeCamera] torchMode];
}

// 设置手电筒模式

- (void)setTorchMode:(AVCaptureTorchMode)torchMode {

    AVCaptureDevice *device = [self activeCamera];

    if (device.torchMode != torchMode &&
        [device isTorchModeSupported:torchMode]) {

        NSError *error;
        if ([device lockForConfiguration:&error]) {
            device.torchMode = torchMode;
            [device unlockForConfiguration];
        } else {
            [self.delegate deviceConfigurationFailedWithError:error];
        }
    }
}
// 获取静态图片
- (void)captureStillImage {

    AVCaptureConnection *connection =                                   
        [self.imageOutput connectionWithMediaType:AVMediaTypeVideo];

    if (connection.isVideoOrientationSupported) {                       
        connection.videoOrientation = [self currentVideoOrientation];
    }

    id handler = ^(CMSampleBufferRef sampleBuffer, NSError *error) {
        if (sampleBuffer != NULL) {

            NSData *imageData =
                [AVCaptureStillImageOutput
                    jpegStillImageNSDataRepresentation:sampleBuffer];

            UIImage *image = [[UIImage alloc] initWithData:imageData];
                                  // 1

        } else {
            NSLog(@"NULL sampleBuffer: %@", [error localizedDescription]);
        }
    };
    // Capture still image   一个异步方法
    [self.imageOutput captureStillImageAsynchronouslyFromConnection:connection
                                                  completionHandler:handler];
}
// 录制视频部分
- (BOOL)isRecording {                                                       // 
    return self.movieOutput.isRecording;
}

- (void)startRecording {

    if (![self isRecording]) {

        AVCaptureConnection *videoConnection =                              // 获取AVCaptureConnection指针
            [self.movieOutput connectionWithMediaType:AVMediaTypeVideo];

        if ([videoConnection isVideoOrientationSupported]) {                // 
            videoConnection.videoOrientation = self.currentVideoOrientation;
        }

        if ([videoConnection isVideoStabilizationSupported]) {              // 
            
            if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) {
                videoConnection.enablesVideoStabilizationWhenAvailable = YES;  // 是否支持视频稳定
            } else {
                videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
            }
        }

        AVCaptureDevice *device = [self activeCamera];

        if (device.isSmoothAutoFocusSupported) {                            // 摄像头平滑对焦操作
            NSError *error;
            if ([device lockForConfiguration:&error]) {
                device.smoothAutoFocusEnabled = NO;
                [device unlockForConfiguration];
            } else {
                [self.delegate deviceConfigurationFailedWithError:error];
            }
        }

        self.outputURL = [self uniqueURL];                                  // 
        [self.movieOutput startRecordingToOutputFileURL:self.outputURL      //  设置代理和视频存放路径 开始录制
                                      recordingDelegate:self];

    }
}

- (CMTime)recordedDuration {
    return self.movieOutput.recordedDuration;
}

- (NSURL *)uniqueURL {                                                      // 

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *dirPath =
        [fileManager temporaryDirectoryWithTemplateString:@"kamera.XXXXXX"];

    if (dirPath) {
        NSString *filePath =
            [dirPath stringByAppendingPathComponent:@"kamera_movie.mov"];
        return [NSURL fileURLWithPath:filePath];
    }

    return nil;
}

- (void)stopRecording {                                                     //  停止录制
    if ([self isRecording]) {
        [self.movieOutput stopRecording];
    }
}

#pragma mark - AVCaptureFileOutputRecordingDelegate

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
      fromConnections:(NSArray *)connections
                error:(NSError *)error {
    if (error) {                                                            
        [self.delegate mediaCaptureFailedWithError:error];
    } else {
        [self writeVideoToAssetsLibrary:[self.outputURL copy]];
    }
    self.outputURL = nil;
}

- (void)writeVideoToAssetsLibrary:(NSURL *)videoURL {

    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];              // 

    if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:videoURL]) {   // 

        ALAssetsLibraryWriteVideoCompletionBlock completionBlock;

        completionBlock = ^(NSURL *assetURL, NSError *error){               // 
            if (error) {
                [self.delegate assetLibraryWriteFailedWithError:error];
            } 
        };

        [library writeVideoAtPathToSavedPhotosAlbum:videoURL                // 写入资源库中
                                    completionBlock:completionBlock];
    }
}
@end

预览层

AVCaptureVideoPreviewLayer需要相关的session(AVCaptureSession)才能激活。

//空间坐标转换的方法。
- (CGPoint)captureDevicePointForPoint:(CGPoint)point {                      
    AVCaptureVideoPreviewLayer *layer =
        (AVCaptureVideoPreviewLayer *)self.layer;
    return [layer captureDevicePointOfInterestForPoint:point];
}
// 重写layerClass方法 返回预览层
+ (Class)layerClass {
    return [AVCaptureVideoPreviewLayer class];
}
- (AVCaptureSession*)session {
    return [(AVCaptureVideoPreviewLayer*)self.layer session];
}
// 设置session会话,关键layer和session。激活相应的layer
- (void)setSession:(AVCaptureSession *)session {
    [(AVCaptureVideoPreviewLayer*)self.layer setSession:session];
}
AVCaptureVideoPreviewLayer的两个坐标转换的方法。
获取屏幕坐标系数据 返回得到额设备坐标系数据
- (CGPoint)captureDevicePointOfInterestForPoint:(CGPoint)pointInLayer;
获取摄像头坐标系的数据,返回转换得到的屏幕坐标系数据
- (CGPoint)pointForCaptureDevicePointOfInterest:(CGPoint)captureDevicePointOfInterest;

AV Foundation开发秘籍源码

相关文章

  • 媒体捕获

    AV Foundation捕捉会话的核心是AVCaptureSession。一个捕捉会话相当于一个虚拟的“插线板”...

  • Still and Video Media Capture -

    Still and Video Media Capture - 静态视频媒体捕获。 To manage the c...

  • Still and Video Media Capture -

    文章目录 1. Still and Video Media Capture - 静态视频媒体捕获。1.1. Use...

  • Still and Video Media Capture

    静态和视频媒体捕获 去管理来自摄像头或麦克风的捕获,你需要装配对象去代表输入和输出,使用一个 AVCaptureS...

  • AVFoundation Programming Guide(学

    介绍使用Assets播放编辑静态视频捕获输出时间和媒体表现 关于AVFoundation AVFoundation...

  • Adobe Premiere Rush CC 2019 for

    Premiere Rush mac破解版软件功能介绍 一、捕获素材或将您自己的媒体导入Premiere Rush ...

  • MacOSX同屏数据的采集(1)

    捕捉媒体的核心是AVCaptureSession捕获会话,通过它来管理咱们的输入设备,它可以同时连接多个输入设备,...

  • OBS-录制流程分析

    obs_output_begin_data_capture 从原始媒体或编码器开始数据捕获,视频/音频数据将开始发...

  • OC之捕获连接 AVCaptureConnection

    AVCaptureConnection 捕获会话中特定捕获输入对和捕获输出对象之间的连接。 捕获输入 AVCap...

  • 分析思考

    听领导建议,每天花费一定时间,观看人民日报、新华社等国内几大媒体,研究每天发布的新闻稿件,捕获媒体热点。 今天,我...

网友评论

      本文标题:媒体捕获

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