IOS音视频(二)AVFoundation视频捕捉

1、 媒体捕捉概念

  • 理解捕捉媒体,需要先了解一些基本概念:

  • 捕捉会话

AVCaptureSession 是管理捕获活动并协调从输入设备到捕获输出的数据流的对象。 AVCaptureSession 用于连接输入和输出的资源,从物理设备如摄像头和麦克风等获取数据流,输出到一个或多个目的地。 AVCaptureSession 可以额外配置一个会话预设值(session preset),用于控制捕捉数据的格式和质量,预设值默认值为 AVCaptureSessionPresetHigh。

要执行实时捕获,需要实例化AVCaptureSession对象并添加适当的输入和输出。下面的代码片段演示了如何配置捕获设备来录制音频。

// Create the capture session.
let captureSession = AVCaptureSession()
​
// Find the default audio device.
guard let audioDevice = AVCaptureDevice.default(for: .audio) else { return }
​
do {
    // Wrap the audio device in a capture device input.
    let audioInput = try AVCaptureDeviceInput(device: audioDevice)
    // If the input can be added, add it to the session.
    if captureSession.canAddInput(audioInput) {
        captureSession.addInput(audioInput)
    }
} catch {
    // Configuration failed. Handle error.
}

您可以调用startRunning()来启动从输入到输出的数据流,并调用stopRunning()来停止该流。

注意:startRunning()方法是一个阻塞调用,可能会花费一些时间,因此应该在串行队列上执行会话设置,以免阻塞主队列(这使UI保持响应)。参见AVCam:构建摄像机应用程序的实现示例

  • 捕捉设备:

AVCaptureDevice 是为捕获会话提供输入(如音频或视频)并为特定于硬件的捕获特性提供控制的设备。它为物理设备定义统一接口,以及大量控制方法,获取指定类型的默认设备方法如下:self.activeVideoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

  1. 一个 AVCaptureDevice 对象表示一个物理捕获设备和与该设备相关联的属性。您可以使用捕获设备来配置底层硬件的属性。捕获设备还向AVCaptureSession对象提供输入数据(如音频或视频)。

  • 捕捉设备的输入:

不能直接将 AVCaptureDevice 加入到 AVCaptureSession 中,需要封装为 AVCaptureDeviceInput

 self.captureVideoInput = [AVCaptureDeviceInput deviceInputWithDevice:self.activeVideoDevice error:&videoError];
    if (self.captureVideoInput) {
        if ([self.captureSession canAddInput:self.captureVideoInput]){
            [self.captureSession addInput:self.captureVideoInput];
        }
    } else if (videoError) {
    }
  • 捕捉输出 :

AVCaptureOutput 作为抽象基类提供了捕捉会话数据流的输出目的地,同时定义了此抽象类的高级扩展类。

  1. AVCaptureStillImageOutput - 静态照片( 在ios10后被废弃,使用AVCapturePhotoOutput代替)

  2. AVCaptureMovieFileOutput - 视频,

  3. AVCaptureAudioFileOutput - 音频

  4. AVCaptureAudioDataOutput - 音频底层数字样本

  5. AVCaptureVideoDataOutput - 视频底层数字样本

  • 捕捉连接

AVCaptureConnection :捕获会话中捕获输入和捕获输出对象的特定对之间的连接。AVCaptureConnection 用于确定哪些输入产生视频,哪些输入产生音频,能够禁用特定连接或访问单独的音频轨道。

  1. 捕获输入有一个或多个输入端口(avcaptureinpu . port的实例)。捕获输出可以接受来自一个或多个源的数据(例如,AVCaptureMovieFileOutput对象同时接受视频和音频数据)。 只有在canAddConnection(:)方法返回true时,才可以使用addConnection(:)方法将AVCaptureConnection实例添加到会话中。当使用addInput(:)或addOutput(:)方法时,会话自动在所有兼容的输入和输出之间形成连接。在添加没有连接的输入或输出时,只需手动添加连接。您还可以使用连接来启用或禁用来自给定输入或到给定输出的数据流。

  • 捕捉预览 :

AVCaptureVideoPreviewLayer 是一个 CALayer 的子类,可以对捕捉视频数据进行实时预览。

2、视频捕捉实例

  • 这个实例的项目代码点击这里下载:OC 视频捕获相机Demo

  • 项目是OC编写的,主要功能实现在THCameraController中,如下图:

  • 主要接口变量在头文件THCameraController.h里面:

#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 用于设置、配置视频捕捉会话
- (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; //闪光灯模式
​
// 4 聚焦、曝光、重设聚焦、曝光的方法
- (void)focusAtPoint:(CGPoint)point;
- (void)exposeAtPoint:(CGPoint)point;
- (void)resetFocusAndExposureModes;
​
// 5 实现捕捉静态图片 & 视频的功能
​
//捕捉静态图片
- (void)captureStillImage;
​
//视频录制
//开始录制
- (void)startRecording;
​
//停止录制
- (void)stopRecording;
​
//获取录制状态
- (BOOL)isRecording;
​
//录制时间
- (CMTime)recordedDuration;
​
@end
  • 我们需要添加访问权限,如果没有获取到相机和麦克风权限,在设置 captureVideoInput 时就会出错。

/// 检测 AVAuthorization 权限
/// 传入待检查的 AVMediaType,AVMediaTypeVideo or AVMediaTypeAudio
/// 返回是否权限可用
- (BOOL)ifAVAuthorizationValid:(NSString *)targetAVMediaType grantedCallback:(void (^)())grantedCallback
{
    NSString *mediaType = targetAVMediaType;
    BOOL result = NO;
    if ([AVCaptureDevice respondsToSelector:@selector(authorizationStatusForMediaType:)]) {
        AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
        switch (authStatus) {
            case AVAuthorizationStatusNotDetermined: { // 尚未请求授权
                [AVCaptureDevice requestAccessForMediaType:targetAVMediaType completionHandler:^(BOOL granted) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        if (granted) {
                            grantedCallback();
                        }
                    });
                }];
                break;
            }
            case AVAuthorizationStatusDenied: { // 明确拒绝
                if ([mediaType isEqualToString:AVMediaTypeVideo]) {
                    [METSettingPermissionAlertView showAlertViewWithPermissionType:METSettingPermissionTypeCamera];// 申请相机权限
                } else if ([mediaType isEqualToString:AVMediaTypeAudio]) {
                    [METSettingPermissionAlertView showAlertViewWithPermissionType:METSettingPermissionTypeMicrophone];// 申请麦克风权限
                }
                break;
            }
            case AVAuthorizationStatusRestricted: { // 限制权限更改
                break;
            }
            case AVAuthorizationStatusAuthorized: { // 已授权
                result = YES;
                break;
            }
            default: // 兜底
                break;
        }
    }
    return result;
}

2.1 创建预览视图

   self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] init];
    [self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
    [self.previewLayer setSession:self.cameraHelper.captureSession];
    self.previewLayer.frame = CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT - 50);
    [self.previewImageView.layer addSublayer:self.previewLayer];
  • 也可以通过 view 的类方法直接换掉 view 的 CALayer 实例:

+ (Class)layerClass {
    return [AVCaptureVideoPreviewLayer class];
}
​
- (AVCaptureSession*)session {
    return [(AVCaptureVideoPreviewLayer*)self.layer session];
}
​
- (void)setSession:(AVCaptureSession *)session {
    [(AVCaptureVideoPreviewLayer*)self.layer setSession:session];
}
  • AVCaptureVideoPreviewLayer 定义了两个方法用于在屏幕坐标系和设备坐标系之间转换,设备坐标系规定左上角为 (0,0),右下角为(1,1)。

  1. (CGPoint)captureDevicePointOfInterestForPoint:(CGPoint)pointInLayer 从屏幕坐标系的点转换为设备坐标系

  2. (CGPoint)pointForCaptureDevicePointOfInterest:(CGPoint)captureDevicePointOfInterest 从设备坐标系的点转换为屏幕坐标系

【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发

【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~

2.2 设置捕捉会话

  • 首先是初始化捕捉会话:

    self.captureSession = [[AVCaptureSession alloc]init];
    [self.captureSession setSessionPreset:(self.isVideoMode)?AVCaptureSessionPreset1280x720:AVCaptureSessionPresetPhoto];
  • 根据拍摄视频还是拍摄照片选择不同的预设值,然后设置会话输入:

- (void)configSessionInput
{
    // 摄像头输入
    NSError *videoError = [[NSError alloc] init];
    self.activeVideoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    self.flashMode = self.activeVideoDevice.flashMode;
    self.captureVideoInput = [AVCaptureDeviceInput deviceInputWithDevice:self.activeVideoDevice error:&videoError];
    if (self.captureVideoInput) {
        if ([self.captureSession canAddInput:self.captureVideoInput]){
            [self.captureSession addInput:self.captureVideoInput];
        }
    } else if (videoError) {
    }
    
    if (self.isVideoMode) {
        // 麦克风输入
        NSError *audioError = [[NSError alloc] init];
        AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio] error:&audioError];
        if (audioInput) {
            if ([self.captureSession canAddInput:audioInput]) {
                [self.captureSession addInput:audioInput];
            }
        } else if (audioError) {
        }
    }
}
  • 对摄像头和麦克风设备均封装为 AVCaptureDeviceInput 后加入到会话中。然后配置会话输出:

- (void)configSessionOutput
{
    if (self.isVideoMode) {
        // 视频输出
        self.movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
        if ([self.captureSession canAddOutput:self.movieFileOutput]) {
            [self.captureSession addOutput:self.movieFileOutput];
        }
    } else {
        // 图片输出
        self.imageOutput = [[AVCaptureStillImageOutput alloc] init];
        self.imageOutput.outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};// 配置 outputSetting 属性,表示希望捕捉 JPEG 格式的图片
        if ([self.captureSession canAddOutput:self.imageOutput]) {
            [self.captureSession addOutput:self.imageOutput];
        }
    }
}
  • 当然你也可以合成在一个方法里面直接设置捕获会话

- (BOOL)setupSession:(NSError **)error {
​
    
    //创建捕捉会话。AVCaptureSession 是捕捉场景的中心枢纽
    self.captureSession = [[AVCaptureSession alloc]init];
    
    /*
     AVCaptureSessionPresetHigh
     AVCaptureSessionPresetMedium
     AVCaptureSessionPresetLow
     AVCaptureSessionPreset640x480
     AVCaptureSessionPreset1280x720
     AVCaptureSessionPresetPhoto
     */
    //设置图像的分辨率
    self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;
    
    //拿到默认视频捕捉设备 iOS系统返回后置摄像头
    AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    
    //将捕捉设备封装成AVCaptureDeviceInput
    //注意:为会话添加捕捉设备,必须将设备封装成AVCaptureDeviceInput对象
    AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
    
    //判断videoInput是否有效
    if (videoInput)
    {
        //canAddInput:测试是否能被添加到会话中
        if ([self.captureSession canAddInput:videoInput])
        {
            //将videoInput 添加到 captureSession中
            [self.captureSession addInput:videoInput];
            self.activeVideoInput = videoInput;
        }
    }else
    {
        return NO;
    }
    
    //选择默认音频捕捉设备 即返回一个内置麦克风
    AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    
    //为这个设备创建一个捕捉设备输入
    AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:error];
   
    //判断audioInput是否有效
    if (audioInput) {
        
        //canAddInput:测试是否能被添加到会话中
        if ([self.captureSession canAddInput:audioInput])
        {
            //将audioInput 添加到 captureSession中
            [self.captureSession addInput:audioInput];
        }
    }else
    {
        return NO;
    }
​
    //AVCaptureStillImageOutput 实例 从摄像头捕捉静态图片
    self.imageOutput = [[AVCaptureStillImageOutput alloc]init];
    
    //配置字典:希望捕捉到JPEG格式的图片
    self.imageOutput.outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
    
    //输出连接 判断是否可用,可用则添加到输出连接中去
    if ([self.captureSession canAddOutput:self.imageOutput])
    {
        [self.captureSession addOutput:self.imageOutput];
        
    }
    
    
    //创建一个AVCaptureMovieFileOutput 实例,用于将Quick Time 电影录制到文件系统
    self.movieOutput = [[AVCaptureMovieFileOutput alloc]init];
    
    //输出连接 判断是否可用,可用则添加到输出连接中去
    if ([self.captureSession canAddOutput:self.movieOutput])
    {
        [self.captureSession addOutput:self.movieOutput];
    }
    
    
    self.videoQueue = dispatch_queue_create("com.kongyulu.VideoQueue", NULL);
    
    return YES;
}

2.3 启动, 停止会话

  • 可以在一个 VC 的生命周期内启动和停止会话,由于这个操作是比较耗时的同步操作,因此建议在异步线程里执行此方法。如下:

- (void)startSession {
​
    //检查是否处于运行状态
    if (![self.captureSession isRunning])
    {
        //使用同步调用会损耗一定的时间,则用异步的方式处理
        dispatch_async(self.videoQueue, ^{
            [self.captureSession startRunning];
        });
    }
}
​
- (void)stopSession {
    
    //检查是否处于运行状态
    if ([self.captureSession isRunning])
    {
        //使用异步方式,停止运行
        dispatch_async(self.videoQueue, ^{
            [self.captureSession stopRunning];
        });
    }
}

2.4 切换摄像头

  • 大多数 ios 设备都有前后两个摄像头,标识前后摄像头需要用到 AVCaptureDevicePositio

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 什么是 AVFoundationAVFoundation是苹果公司提供的一个多媒体处理框架,它能够处理音频、视频、文本和图像等媒体类型,还能够实现录制、编辑、播放等多种操作。 2. AVFoundation的优点是什么? AVFoundation有以下几个优点: - 它可以在多个平台上使用,包括iOS、macOS和tvOS等。 - 它提供了灵活的API,可以对多种媒体类型进行处理。 - 它支持硬件加速,能够提高处理速度和性能。 - 它支持多种格式的媒体文件,包括MP3、AAC、H.264和MPEG-4等。 3. 如何使用AVFoundation实现音频录制? 在使用AVFoundation实现音频录制时,需要执行以下步骤: - 创建一个AVAudioSession对象,用于管理音频会话。 - 创建一个AVAudioRecorder对象,用于录制音频。 - 配置录音参数,例如音频格式、采样率、通道数、音频质量等。 - 调用AVAudioRecorder的record方法开始录音。 - 调用AVAudioRecorder的stop方法停止录音。 4. AVFoundation中的AVPlayerLayer是什么? AVPlayerLayer是一个CALayer子类,用于在iOS和macOS应用程序中显示视频内容。它可以显示一个AVPlayer对象的输出,并且支持全屏播放、画中画、视频内容缩放等功能。 5. 如何使用AVFoundation实现视频播放? 在使用AVFoundation实现视频播放时,需要执行以下步骤: - 创建一个AVPlayer对象,用于播放视频。 - 创建一个AVPlayerLayer对象,用于显示视频内容。 - 将AVPlayerLayer对象添加到视图层次结构中。 - 创建一个AVPlayerItem对象,用于管理视频资源。 - 调用AVPlayer的replaceCurrentItemWithPlayerItem方法将AVPlayerItem与AVPlayer关联。 - 调用AVPlayer的play方法开始播放视频。 6. 如何在AVFoundation中实现视频编辑? 在AVFoundation中实现视频编辑通常需要使用AVAsset、AVAssetTrack、AVComposition、AVMutableComposition等类。以下是实现视频编辑的大致步骤: - 创建一个AVAsset对象,用于表示视频资源。 - 创建一个AVMutableComposition对象,用于管理视频资源。 - 使用AVAssetTrack获取视频的音频和视频轨道。 - 使用AVMutableCompositionTrack将音频和视频轨道添加到AVMutableComposition中。 - 使用AVAssetExportSession导出编辑后的视频。 7. AVFoundation中的AVCaptureSession是什么? AVCaptureSession是用于管理视频和音频输入的会话对象。它可以管理多个输入设备(例如摄像头、麦克风等)并且可以将它们合并到单个输出中。使用AVCaptureSession可以方便地实现视频录制、视频流传输、实时视频分析等功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值