当前位置: 代码网 > it编程>App开发>苹果IOS > AVFoundation AVCaptureSession媒体捕捉

AVFoundation AVCaptureSession媒体捕捉

2024年05月18日 苹果IOS 我要评论
正文avfoundation 是apple ios和os x系统中用于处理基于时间的媒体数据的高级框架,通过开发所需的工具提供了强大的功能集,让开发者能够基于苹果平台创建当下最先进的媒体应用程序,其针

正文

avfoundation 是apple ios和os x系统中用于处理基于时间的媒体数据的高级框架,通过开发所需的工具提供了强大的功能集,让开发者能够基于苹果平台创建当下最先进的媒体应用程序,其针对64位处理器设计,充分利用了多核硬件优势,会自动提供硬件加速操作,确保大部分设备能以最佳性能运行,是ios开发接触音视频开发必学的框架之一

参与掘金日新计划,持续记录avfoundation学习,demo学习地址,里面封装了一些工具类,可以直接使用,这篇文章主要讲述avfoundation中的avcapturesession等类实现媒体捕捉功能,其他类的相关用法可查看我的其他文章。

捕捉媒体

媒体捕捉是avfoundation的核心功能之一,也是开发音视频app必不可少的功能。捕捉用到的类如图所示

  • avcapturesession是avfoundation捕捉栈的核心类,捕捉会话用于连接输入和输出资源,管理从物理设备得到的输入流,例如从摄像头得到的视频从麦克风得到的音频,以不同的方式输出给一个或多个输出,可以动态配置输入和输出线路,让开发者能够在会话中按需重新配置捕捉环境。
  • avcapturedevice为摄像头麦克风等物理设备定义了一个接口,在ios10以后,使用avcapturedevicediscoverysession获取设备。
  • avcapturedevice包装成avcapturedeviceinput才能添加到捕捉回话中。
  • avfoundation定义了avcaptureoutput的许多扩展类,avcaptureoutput是一个抽象基类,用于从捕捉回话中得到数据,框架定义了这个抽象类的高级扩展类,常用的有avcapturestillimageoutput静态图片输出、avcapturemoviefileoutput视频文件输出、avcapturevideodataoutput视频流数据输出、avcaptureaudiodataoutput音频流数据输出、avcapturemetadataoutput元数据输出。注意,不能同时配置avcapturevideodataoutputavcapturemoviefileoutput,二者无法同时启用。
  • avcapturevideopreviewlayer是coreanimation框架中calayer的一个子类,对捕捉视频数据实时预览。当然也可以使用glkviewuiimageview预览实时视频流的buffer。

具体代码可以看demo中的cqcapturemanager类对捕捉工具的封装

1.创建会话

创建会话并配置分辨率

  • 配置分辨率注意要判断下能否支持,例如老机型前置摄像头配置4k是不支持的。
  • 不同分辨率的缩放倍数也是不同的
self.capturesession = [[avcapturesession alloc] init];
- (void)configsessionpreset:(avcapturesessionpreset)sessionpreset {
    [self.capturesession beginconfiguration];
    if ([self.capturesession cansetsessionpreset:sessionpreset])  {
        self.capturesession.sessionpreset = sessionpreset;
    } else {
        self.capturesession.sessionpreset = avcapturesessionpresethigh;
    }
    [self.capturesession commitconfiguration];
    self.isconfigsessionpreset = yes;
}

2.配置视频输入

/// 配置视频输入
- (bool)configvideoinput:(nserror * _nullable *)error {
    // 添加视频捕捉设备
    // 拿到默认视频捕捉设备 ios默认后置摄像头
//    avcapturedevice *videodevice = [avcapturedevice defaultdevicewithmediatype:avmediatypevideo];
    avcapturedevice *videodevice = [self getcamerawithposition:avcapturedevicepositionback];
    // 将捕捉设备转化为avcapturedeviceinput
    // 注意:会话不能直接使用avcapturedevice,必须将avcapturedevice封装成avcapturedeviceinput对象
    avcapturedeviceinput *videoinput = [avcapturedeviceinput deviceinputwithdevice:videodevice error:error];
    // 将捕捉设备添加给会话
    // 使用前判断videoinput是否有效以及能否添加,因为摄像头是一个公共设备,不属于任何app,有可能别的app在使用,添加前应该先进行判断是否可以添加
    if (videoinput && [self.capturesession canaddinput:videoinput]) {
        // 将videoinput 添加到 capturesession中
        [self.capturesession beginconfiguration];
        [self.capturesession addinput:videoinput];
        [self.capturesession commitconfiguration];
        self.videodeviceinput = videoinput;
        return yes;
    }else {
        return no;
    }
}
/// 移除视频输入设备
- (void)removevideodeviceinput {
    if (self.videodeviceinput) [self.capturesession removeinput:self.videodeviceinput];
    self.videodeviceinput = nil;
}
  • 获取摄像头,ios10之后使用avcapturedevicediscoverysession获取
  • 长焦超广或者双摄三摄必须使用avcapturedevicediscoverysession获取,[avcapturedevice deviceswithmediatype:avmediatypevideo]无法获取
/// 根据position拿到摄像头
- (avcapturedevice *)getcamerawithposition:(avcapturedeviceposition)position {
    /**
     avcapturedevicetypebuiltinwideanglecamera 广角(默认设备,28mm左右焦段)
     avcapturedevicetypebuiltintelephotocamera 长焦(默认设备的2x或3x,只能使用avcapturedevicediscoverysession获取)
     avcapturedevicetypebuiltinultrawidecamera 超广角(默认设备的0.5x,只能使用avcapturedevicediscoverysession获取)
     avcapturedevicetypebuiltindualcamera (一个广角一个长焦(iphone7p,iphonex),可以自动切换摄像头,只能使用avcapturedevicediscoverysession获取)
     avcapturedevicetypebuiltindualwidecamera (一个超广一个广角(iphone12 iphone13),可以自动切换摄像头,只能使用avcapturedevicediscoverysession获取)
     avcapturedevicetypebuiltintriplecamera (超广,广角,长焦三摄像头,iphone11promax iphone12promax iphone13promax,可以自动切换摄像头,只能使用avcapturedevicediscoverysession获取)
     avcapturedevicetypebuiltintruedepthcamera (红外和摄像头, iphone12promax iphone13promax )
     */
    nsarray *devicetypes;
    if (position == avcapturedevicepositionback) {
        devicetypes = @[avcapturedevicetypebuiltindualcamera,
                        avcapturedevicetypebuiltindualwidecamera,
                        avcapturedevicetypebuiltintriplecamera, ];
    } else {
        devicetypes = @[avcapturedevicetypebuiltinwideanglecamera];
    }
    avcapturedevicediscoverysession *devicesession = [avcapturedevicediscoverysession discoverysessionwithdevicetypes:devicetypes mediatype:avmediatypevideo position:position];
    if (devicesession.devices.count) return devicesession.devices.firstobject;
    if (position == avcapturedevicepositionback) {
        // 非多摄手机
        devicetypes = @[avcapturedevicetypebuiltinwideanglecamera];
        avcapturedevicediscoverysession *devicesession = [avcapturedevicediscoverysession discoverysessionwithdevicetypes:devicetypes mediatype:avmediatypevideo position:position];
        if (devicesession.devices.count) return devicesession.devices.firstobject;
    }
    return nil;
}

3.配置音频输入

/// 配置音频输入
- (bool)configaudioinput:(nserror * _nullable *)error {
    // 添加音频捕捉设备 ,如果只是拍摄静态图片,可以不用设置
    // 选择默认音频捕捉设备 即返回一个内置麦克风
    avcapturedevice *audiodevice = [avcapturedevice defaultdevicewithmediatype:avmediatypeaudio];
    self.audiodeviceinput = [avcapturedeviceinput deviceinputwithdevice:audiodevice error:error];
    if (self.audiodeviceinput && [self.capturesession canaddinput:self.audiodeviceinput]) {
        [self.capturesession beginconfiguration];
        [self.capturesession addinput:self.audiodeviceinput];
        [self.capturesession commitconfiguration];
        return yes;
    }else {
        return no;
    }
}
/// 移除音频输入设备
- (void)removeaudiodeviceinput {
    if (self.audiodeviceinput) [self.capturesession removeinput:self.audiodeviceinput];
}

5.配置输出

#pragma mark - func 静态图片输出配置
/// 配置静态图片输出
- (void)configstillimageoutput {
    // avcapturestillimageoutput 从摄像头捕捉静态图片
    self.stillimageoutput = [[avcapturestillimageoutput alloc] init];
    // 配置字典:希望捕捉到jpeg格式的图片
    self.stillimageoutput.outputsettings = @{avvideocodeckey:avvideocodecjpeg};
    // 输出连接 判断是否可用,可用则添加到输出连接中去
    [self.capturesession beginconfiguration];
    if ([self.capturesession canaddoutput:self.stillimageoutput]) {
        [self.capturesession addoutput:self.stillimageoutput];
    }
    [self.capturesession commitconfiguration];
}
/// 移除静态图片输出
- (void)removestillimageoutput {
    if (self.stillimageoutput) [self.capturesession removeoutput:self.stillimageoutput];
}
#pragma mark - func 电影文件输出配置
/// 配置电影文件输出
- (void)configmoviefileoutput {
    // avcapturemoviefileoutput,将quicktime视频录制到文件系统
    self.moviefileoutput = [[avcapturemoviefileoutput alloc] init];
    [self.capturesession beginconfiguration];
    if ([self.capturesession canaddoutput:self.moviefileoutput]) {
        [self.capturesession addoutput:self.moviefileoutput];
    }
    [self.capturesession commitconfiguration];
}
/// 移除电影文件输出
- (void)removemoviefileoutput {
    if (self.moviefileoutput) [self.capturesession removeoutput:self.moviefileoutput];
}

6.开始会话\结束会话

// 异步开始会话
- (void)startsessionasync {
    // 检查是否处于运行状态
    if (![self.capturesession isrunning]) {
        // 使用同步调用会损耗一定的时间,则用异步的方式处理
        dispatch_async(self.capturevideoqueue, ^{
            [self.capturesession startrunning];
        });
    }
}
// 异步停止会话
- (void)stopsessionasync {
    // 检查是否处于运行状态
    if ([self.capturesession isrunning]) {
        dispatch_async(self.capturevideoqueue, ^{
            [self.capturesession stoprunning];
        });
    }
}

7.捕捉静态图片

#pragma mark - 静态图片捕捉
#pragma mark public func 静态图片捕捉
// 捕捉静态图片
- (void)capturestillimage {
    if (!self.isconfigsessionpreset) [self configsessionpreset:avcapturesessionpresetmedium];
    if (!self.videodeviceinput) {
        nserror *configerror;
        bool configresult = [self configvideoinput:&configerror];
        if (!configresult) return;
    }
    if (!self.stillimageoutput) [self configstillimageoutput];
    [self startsessionsync];
    // 获取图片输出连接
    avcaptureconnection *connection = [self.stillimageoutput connectionwithmediatype:avmediatypevideo];
    // 即使程序只支持纵向,但是如果用户横向拍照时,需要调整结果照片的方向
    // 判断是否支持设置视频方向, 支持则根据设备方向设置输出方向值
    if (connection.isvideoorientationsupported) {
        connection.videoorientation = [self getcurrentvideoorientation];
    }
    [self.stillimageoutput capturestillimageasynchronouslyfromconnection:connection completionhandler:^(cmsamplebufferref  _nullable imagedatasamplebuffer, nserror * _nullable error) {        if (imagedatasamplebuffer != null) {            dispatch_async(dispatch_get_main_queue(), ^{                if (self.delegate && [self.delegate respondstoselector:@selector(mediacaptureimagefilesuccess)]) {
                    [self.delegate mediacaptureimagefilesuccess];
                }
            });
            // cmsamplebufferref转uiimage 并写入相册
            nsdata *imagedata = [avcapturestillimageoutput jpegstillimagensdatarepresentation:imagedatasamplebuffer];
            uiimage *image = [[uiimage alloc] initwithdata:imagedata];
            [self writeimagetoassetslibrary:image];
        } else {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (self.delegate && [self.delegate respondstoselector:@selector(mediacaptureimagefailedwitherror:)]) {
                    [self.delegate mediacaptureimagefailedwitherror:error];
                }
            });
            nslog(@"null samplebuffer:%@",[error localizeddescription]);
        }
    }];
}
#pragma mark private func 静态图片捕捉
/**
 assets library 框架
 用来让开发者通过代码方式访问ios photo
 注意:会访问到相册,需要修改plist 权限。否则会导致项目崩溃
 */
/// 将uiimage写入到用户相册
- (void)writeimagetoassetslibrary:(uiimage *)image {
    alassetslibrary *library = [[alassetslibrary alloc] init];
    // 参数1 图片, 参数2 方向, 参数3 回调
    [library writeimagetosavedphotosalbum:image.cgimage orientation:(nsuinteger)image.imageorientation completionblock:^(nsurl *asseturl, nserror *error) {        if (!error) {            dispatch_async(dispatch_get_main_queue(), ^{                if (self.delegate && [self.delegate respondstoselector:@selector(assetlibrarywriteimagesuccesswithimage:)]) {
                    [self.delegate assetlibrarywriteimagesuccesswithimage:image];
                }
            });
        } else {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (self.delegate && [self.delegate respondstoselector:@selector(assetlibrarywriteimagefailedwitherror:)]) {
                    [self.delegate assetlibrarywriteimagefailedwitherror:error];
                }
            });
        }
    }];
}

8.捕捉视频文件

#pragma mark - 电影文件捕捉
#pragma mark public func 电影文件捕捉
// 开始录制电影文件
- (void)startrecordingmoviefile {
    if (!self.isconfigsessionpreset) [self configsessionpreset:avcapturesessionpresetmedium];
    if (!self.videodeviceinput) {
        nserror *configerror;
        bool configresult = [self configvideoinput:&configerror];
        if (!configresult) return;
    }
    if (!self.moviefileoutput) [self configmoviefileoutput];
    [self startsessionsync];
    if ([self isrecordingmoviefile]) return;
    avcaptureconnection *videoconnection = [self.moviefileoutput connectionwithmediatype:avmediatypevideo];
    // 设置输出方向
    // 即使程序只支持纵向,但是如果用户横向拍照时,需要调整结果照片的方向
    // 判断是否支持设置视频方向, 支持则根据设备方向设置输出方向值
    if (videoconnection.isvideoorientationsupported) {
        videoconnection.videoorientation = [self getcurrentvideoorientation];
    }
    // 设置视频帧稳定
    // 判断是否支持视频稳定 可以显著提高视频的质量。只会在录制视频文件涉及
//    if (videoconnection.isvideostabilizationsupported) {
//        videoconnection.enablesvideostabilizationwhenavailable = yes;
//    }
    videoconnection.preferredvideostabilizationmode = avcapturevideostabilizationmodeauto;
    // 设置对焦
    avcapturedevice *device = [self getactivecamera];
    // 摄像头可以进行平滑对焦模式操作。即减慢摄像头镜头对焦速度。当用户移动拍摄时摄像头会尝试快速自动对焦。
    if (device.issmoothautofocusenabled) {
        nserror *error;
        if ([device lockforconfiguration:&error]) {
            device.smoothautofocusenabled = yes;
            [device unlockforconfiguration];
        } else {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (self.delegate && [self.delegate respondstoselector:@selector(deviceconfigurationfailedwitherror:)]) {
                    [self.delegate deviceconfigurationfailedwitherror:error];
                }
            });
        }
    }
    self.moviefileoutputurl = [self getvideotemppathurl];
    // 开始录制 参数1:录制保存路径  参数2:代理
    [self.moviefileoutput startrecordingtooutputfileurl:self.moviefileoutputurl recordingdelegate:self];
}
// 停止录制电影文件
- (void)stoprecordingmoviefile {
    if ([self isrecordingmoviefile]) {
        [self.moviefileoutput stoprecording];
    }
}
// 是否在录制电影文件
- (bool)isrecordingmoviefile {
    return self.moviefileoutput.isrecording;
}
// 录制电影文件的时间
- (cmtime)moviefilerecordedduration {
    return self.moviefileoutput.recordedduration;
}
#pragma mark avcapturefileoutputrecordingdelegate
/// 捕捉电影文件成功的回调
- (void)captureoutput:(avcapturefileoutput *)output didfinishrecordingtooutputfileaturl:(nsurl *)outputfileurl fromconnections:(nsarray<avcaptureconnection *> *)connections error:(nserror *)error {
    if (error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (self.delegate && [self.delegate respondstoselector:@selector(mediacapturemoviefilefailedwitherror:)]) {
                [self.delegate mediacapturemoviefilefailedwitherror:error];
            }
        });
    } else {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (self.delegate && [self.delegate respondstoselector:@selector(mediacapturemoviefilesuccess)]) {
                [self.delegate mediacapturemoviefilesuccess];
            }
        });
        // copy一个副本再置为nil
        // 将文件写入相册
        [self writevideotoassetslibrary:self.moviefileoutputurl.copy];
        self.moviefileoutputurl = nil;
    }
}
#pragma mark private func 电影文件捕捉
/// 创建视频文件临时路径url
- (nsurl *)getvideotemppathurl {
    nsfilemanager *filemanager = [nsfilemanager defaultmanager];
    nsstring *temppath = [filemanager temporarydirectorywithtemplatestring:@"video.xxxxxx"];
    if (temppath) {
        nsstring *filepath = [temppath stringbyappendingpathcomponent:@"temp_video.mov"];
        return [nsurl fileurlwithpath:filepath];
    }
    return nil;
}
/// 将视频文件写入到用户相册
- (void)writevideotoassetslibrary:(nsurl *)videourl {
    alassetslibrary *library = [[alassetslibrary alloc] init];
    // 和图片不同,视频的写入更耗时,所以写入之前应该判断是否能写入
    if (![library videoatpathiscompatiblewithsavedphotosalbum:videourl]) return;
    [library writevideoatpathtosavedphotosalbum:videourl completionblock:^(nsurl *asseturl, nserror *error) {
        if (error) {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (self.delegate && [self.delegate respondstoselector:@selector(assetlibrarywritemoviefilefailedwitherror:)]) {
                    [self.delegate assetlibrarywritemoviefilefailedwitherror:error];
                }
            });
        } else {
            // 写入成功 回调封面图
            [self getvideocoverimagewithvideourl:videourl callblock:^(uiimage *coverimage) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (self.delegate && [self.delegate respondstoselector:@selector(assetlibrarywritemoviefilesuccesswithcoverimage:)]) {
                        [self.delegate assetlibrarywritemoviefilesuccesswithcoverimage:coverimage];
                    }
                });
            }];
        }
    }];
}
/// 获取视频文件封面图
- (void)getvideocoverimagewithvideourl:(nsurl *)videourl callblock:(void(^)(uiimage *))callblock {
    dispatch_async(self.capturevideoqueue, ^{
        avasset *asset = [avasset assetwithurl:videourl];
        avassetimagegenerator *imagegenerator = [avassetimagegenerator assetimagegeneratorwithasset:asset];
        // 设置maximumsize 宽为100,高为0 根据视频的宽高比来计算图片的高度
        imagegenerator.maximumsize = cgsizemake(100.0f, 0.0f);
        // 捕捉视频缩略图会考虑视频的变化(如视频的方向变化),如果不设置,缩略图的方向可能出错
        imagegenerator.appliespreferredtracktransform = yes;
        cgimageref imageref = [imagegenerator copycgimageattime:kcmtimezero actualtime:null error:nil];
        uiimage *image = [uiimage imagewithcgimage:imageref];
        cgimagerelease(imageref);
        dispatch_async(dispatch_get_main_queue(), ^{
            !callblock ?: callblock(image);
        });
    });
}

9.预览视频

previewview.session = capturemanager.capturesession

以上就是avfoundation avcapturesession媒体捕捉的详细内容,更多关于avfoundation avcapturesession的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com