欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS 录像功能的简单实现

程序员文章站 2022-05-26 16:05:56
话不多说,上 demo 这里用的是 svproresshud, 由于 ios10 的权限缘故,需要在 plist 里添加字段,否则会崩溃,具体请看上一篇 // // viewcontroll...

话不多说,上 demo 这里用的是 svproresshud, 由于 ios10 的权限缘故,需要在 plist 里添加字段,否则会崩溃,具体请看上一篇

//
//  viewcontroller.m
//  录制视频
//
//  created by amydom on 16/8/25.
//  copyright © 2016年 amydom. all rights reserved.
//

#import "viewcontroller.h"
#import 
#import 
#import 

#import 
#import 
#import 
#import 

#import "svprogresshud.h"

static nsstring *const assetcollectionname = @"录制视频";
@interface viewcontroller ()
@property (weak, nonatomic) uiimageview *centerframeimageview;
@property (weak, nonatomic)  uilabel *videodurationlabel;
@property (nonatomic, assign) bool shouldasync;


@end

@implementation viewcontroller

- (void)viewdidload {
    [super viewdidload];
    [self createbtn];
#pragma mark - 视频相关
    /*
     一.保存图片到【camera roll】(相机胶卷)
     1.使用函数uiimagewritetosavedphotosalbum
     2.使用assetslibrary.framework(ios9开始, 已经过期)
     3.使用photos.framework(ios8开始可以使用, 从ios9开始完全取代assetslibrary.framework)
     
     二.创建新的【自定义album】(相簿\相册)
     1.使用assetslibrary.framework(ios9开始, 已经过期)
     2.使用photos.framework(ios8开始可以使用, 从ios9开始完全取代assetslibrary.framework)
     
     三.将【camera roll】(相机胶卷)的图片 添加到 【自定义album】(相簿\相册)中
     1.使用assetslibrary.framework(ios9开始, 已经过期)
     2.使用photos.framework(ios8开始可以使用, 从ios9开始完全取代assetslibrary.framework)
     
     四.photos.framework须知
     1.phasset : 一个phasset对象就代表一张图片或者一段视频
     2.phassetcollection : 一个phassetcollection对象就代表一本相册
     
     五.phassetchangerequest的基本认识
     1.可以对相册图片进行【增\删\改】的操作
     
     六.phphotolibrary的基本认识
     1.对相册的任何修改都必须放在以下其中一个方法的block中
     [[phphotolibrary sharedphotolibrary] performchangesandwait:error:];
     [[phphotolibrary sharedphotolibrary] performchanges:completionhandler:];
     */

    
}

- (void)createbtn{
    
    // 录制视频
    uibutton *recordvideo = [[uibutton alloc]initwithframe:cgrectmake(100, 100, 100, 100)];
    [recordvideo settitle:@"开始录制" forstate:uicontrolstatenormal];
    recordvideo.backgroundcolor = [uicolor lightgraycolor];
    [recordvideo addtarget:self action:@selector(videofromcamera) forcontrolevents:uicontroleventtouchupinside];
    [self.view addsubview:recordvideo];
    
    // 从选择视频
    uibutton *selectlocalvideo = [[uibutton alloc]initwithframe:cgrectmake(100, 250, 100, 100)];
    [selectlocalvideo settitle:@"选择视频" forstate:uicontrolstatenormal];
    selectlocalvideo.backgroundcolor = [uicolor lightgraycolor];
    [selectlocalvideo addtarget:self action:@selector(videofromphotos) forcontrolevents:uicontroleventtouchupinside];
    [self.view addsubview:selectlocalvideo];
}

// 录制视频
- (void)videofromcamera{
        [self getvideowithsourcetype:uiimagepickercontrollersourcetypecamera shouldasync:yes];
    
}

// 从相册中选择视频"
- (void)videofromphotos{
    //uiimagepickercontrollersourcetypesavedphotosalbum - 这个是自定义库,是由用户截图或保存到里面的
        [self getvideowithsourcetype:uiimagepickercontrollersourcetypesavedphotosalbum shouldasync:no];
}
//调用摄像头
- (void)getvideowithsourcetype:(uiimagepickercontrollersourcetype)type shouldasync:(bool)shouldasync{
    //取得授权状态
    avauthorizationstatus authstatus = [avcapturedevice authorizationstatusformediatype:avmediatypevideo];
    //判断当前状态
    if (authstatus == avauthorizationstatusrestricted
        || authstatus == avauthorizationstatusdenied) {
        //拒绝当前 app 访问[phtot]运行
        [svprogresshud showinfowithstatus:@"提醒用户打开访问开关 [设置] - [隐私] - [视频] - [app]"];
        return;
    }
    
    if ([uiimagepickercontroller issourcetypeavailable:type]) {
        uiimagepickercontroller *picker = [[uiimagepickercontroller alloc]init];
        picker.delegate = self;
        //可以编辑
        picker.allowsediting = yes;
        //设置资源获取类型
        picker.sourcetype = type;
        picker.mediatypes = @[(nsstring *)kuttypemovie];
        [self presentviewcontroller:picker animated:yes completion:null];
        self.shouldasync = shouldasync;
        
    }else{
        
        [svprogresshud showinfowithstatus:@"手机不支持摄像"];
        
    }
}
#pragma mark - uiimagepickercontrollerdelegate

- (void)imagepickercontroller:(uiimagepickercontroller *)picker didfinishpickingmediawithinfo:(nsdictionary *)info{
    //获取媒体 url
    nsurl * videourl = [info objectforkey:uiimagepickercontrollermediaurl];
    //判断版本号
    if ([uidevice currentdevice].systemversion.doublevalue < 9.0) {
        alassetslibrary * library = [[alassetslibrary alloc]init];
        //创建并行队列
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            //判断相册时候兼容视频,兼容才能保存到相册
            if ([library videoatpathiscompatiblewithsavedphotosalbum:videourl]) {
                [library writevideoatpathtosavedphotosalbum:videourl completionblock:^(nsurl *asseturl, nserror *error) {
                    
                    dispatch_async(dispatch_get_main_queue(), ^{
                        //回到主线程,写入相册
                        if (error == nil) {
                            avurlasset *videoasset = [[avurlasset alloc]initwithurl:asseturl options:nil];
                            float64 duration = cmtimegetseconds(videoasset.duration);
                            self.videodurationlabel.text = [nsstring stringwithformat:@"视频时长: %.0f秒",duration];
                            if (_shouldasync) {
                                __weak __typeof(self)weakself = self;
                                // get center frame image asyncly
                                [weakself centerframeimagewithvideourl:videourl completion:^(uiimage *image) {
                                    
                                    weakself.centerframeimageview.image = image;
                                    
                                }];
                                
                            } else {
                                
                                //同步获取中间帧图片
                                uiimage * image = [self frameimagefromvideourl:videourl];
                                self.centerframeimageview.image = image;
                            }
                            
                            // begin to compress and export the video to the output path
                            // 开始压缩和导出视频输出路径
                            nsstring * name = [[nsdate date] description];
                            name = [nsstring stringwithformat:@"%@.mp4", name];
                            [self compressvideowithvideourl:videourl savedname:name completion:^(nsstring *savedpath) {
                                
                                if (savedpath) {
                                    
                                     nslog(@"compressed successfully. path: %@", savedpath);
                                } else {
                                    
                                    nslog(@"compressed failed");
                                }
                            }];
                            
                            [svprogresshud showinfowithstatus:@"保存视频失败"];
                            
                        } else {
                            
                            [svprogresshud showinfowithstatus:@"保存视频成功"];
                        }
                        
                    });
                    
                    
                }];
                
            }
            
        });
        
    }else{
        //9.0以后
        phphotolibrary * library = [phphotolibrary sharedphotolibrary];
        dispatch_async(dispatch_get_main_queue(), ^{
            
            nserror * error = nil;
            // 用来抓取phasset的字符串标识
            __block nsstring *assetid = nil;
            // 用来抓取phassetcollectin的字符串标识符
            __block nsstring *assetcollectionid = nil;
            
            // 保存视频到【camera roll】(相机胶卷)
            
            [library performchangesandwait:^{
                
                assetid = [phassetchangerequest creationrequestforassetfromvideoatfileurl:videourl].placeholderforcreatedasset.localidentifier;
                
            } error:&error];
            // 获取曾经创建过的自定义视频相册名字
            phassetcollection  * createdassetcollection = nil;
            phfetchresult< phassetcollection *>* assetcollections = [phassetcollection fetchassetcollectionswithtype:phassetcollectiontypealbum subtype:phassetcollectionsubtypealbumregular options:nil];
            for (phassetcollection * assetcollection in assetcollections) {
                if ([assetcollection.localizedtitle isequaltostring: assetcollectionname]) {
                    
                    createdassetcollection = assetcollection;
                    
                    break;
                    
                }
            }
            
            //如果这个自定义框架没有创建过
            if (createdassetcollection == nil) {
                //创建新的[自定义的 album](相簿\相册)
                [library performchangesandwait:^{
                    
                    assetcollectionid = [phassetcollectionchangerequest creationrequestforassetcollectionwithtitle:assetcollectionname].placeholderforcreatedassetcollection.localidentifier;
                    
                    
                } error:&error];
                
                //抓取刚创建完的视频相册对象
                createdassetcollection = [phassetcollection fetchassetcollectionswithlocalidentifiers:@[assetcollectionid] options:nil].firstobject;
            }
            
             // 将【camera roll】(相机胶卷)的视频 添加到 【自定义album】(相簿\相册)中
            [library performchangesandwait:^{
                phassetcollectionchangerequest *request = [phassetcollectionchangerequest changerequestforassetcollection:createdassetcollection];
                
                // 视频
                [request addassets:[phasset fetchassetswithlocalidentifiers:@[assetid] options:nil]];
            } error:&error];
            
            // 提示信息
            if (error) {
                [svprogresshud showerrorwithstatus:@"保存视频失败!"];
            } else {
                [svprogresshud showsuccesswithstatus:@"保存视频成功!"];
            }

        });
        
        
    }
    
    [picker dismissviewcontrolleranimated:yes completion:^{
        // for fixing ios 8.0 problem that frame changed when open camera to record video.
        self.tabbarcontroller.view.frame  = [[uiscreen mainscreen] bounds];
        [self.tabbarcontroller.view layoutifneeded];
    }];
    
}
#pragma mark - 同步获取帧图
// get the video's center frame as video poster image
- (uiimage *)frameimagefromvideourl:(nsurl *)videourl {
    // result
    uiimage *image = nil;
    
    // avassetimagegenerator
    avasset *asset = [avasset assetwithurl:videourl];
    avassetimagegenerator *imagegenerator = [[avassetimagegenerator alloc] initwithasset:asset];
    imagegenerator.appliespreferredtracktransform = yes;
    
    // calculate the midpoint time of video
    float64 duration = cmtimegetseconds([asset duration]);
    // 取某个帧的时间,参数一表示哪个时间(秒),参数二表示每秒多少帧
    // 通常来说,600是一个常用的公共参数,苹果有说明:
    // 24 frames per second (fps) for film, 30 fps for ntsc (used for tv in north america and
    // japan), and 25 fps for pal (used for tv in europe).
    // using a timescale of 600, you can exactly represent any number of frames in these systems
    cmtime midpoint = cmtimemakewithseconds(duration / 2.0, 600);
    
    // get the image from
    nserror *error = nil;
    cmtime actualtime;
    // returns a cfretained cgimageref for an asset at or near the specified time.
    // so we should mannully release it
    cgimageref centerframeimage = [imagegenerator copycgimageattime:midpoint
                                                         actualtime:&actualtime
                                                              error:&error];
    
    if (centerframeimage != null) {
        image = [[uiimage alloc] initwithcgimage:centerframeimage];
        // release the cfretained image
        cgimagerelease(centerframeimage);
    }
    
    return image;
}

#pragma mark - 异步获取帧图
//异步获取帧图,可以一次回去多帧图片
- (void)centerframeimagewithvideourl:(nsurl *)videourl completion:(void (^)(uiimage *image))completion{
    
    // avassetimagegenerator
    avasset * asset = [avasset assetwithurl:videourl];
    avassetimagegenerator *imagegenerator = [[avassetimagegenerator alloc]initwithasset:asset];
    imagegenerator.appliespreferredtracktransform = yes;
    // calculate the midpoint time of video
    float64 duration = cmtimegetseconds([asset duration]);
    
    // 取某个帧的时间,参数一表示哪个时间(秒),参数二表示每秒多少帧
    // 通常来说,600是一个常用的公共参数,苹果有说明:
    // 24 frames per second (fps) for film, 30 fps for ntsc (used for tv in north america and
    // japan), and 25 fps for pal (used for tv in europe).
    // using a timescale of 600, you can exactly represent any number of frames in these systems
    
    cmtime midpoint = cmtimemakewithseconds(duration / 2.0, 600);
    //异步获取多帧图
    nsvalue * midtime = [nsvalue valuewithcmtime:midpoint];
    [imagegenerator generatecgimagesasynchronouslyfortimes:@[midtime] completionhandler:^(cmtime requestedtime, cgimageref  _nullable image, cmtime actualtime, avassetimagegeneratorresult result, nserror * _nullable error) {
        
        if (result == avassetimagegeneratorsucceeded && image != null) {
            uiimage * centerframeimage = [[uiimage alloc]initwithcgimage:image];
            dispatch_async(dispatch_get_main_queue(), ^{
                
                if (completion) {
                    
                    completion(centerframeimage);
                    
                }
                
            });
            
        } else {
            
            dispatch_async(dispatch_get_main_queue(), ^{
                
                if (completion) {
                    completion(nil);
                }
            });
            
            
        }
        
    }];
  
}

#pragma mark - 压缩导出视频
- (void)compressvideowithvideourl:(nsurl *)videourl
                        savedname:(nsstring *)savedname
                       completion:(void (^)(nsstring *savedpath))completion{
    
    // accessing video by url
    avurlasset *videoasset = [[avurlasset alloc] initwithurl:videourl options:nil];
    // find compatible presets by video asset.
    nsarray *presets = [avassetexportsession exportpresetscompatiblewithasset:videoasset];
    // begin to compress video
    // now we just compress to low resolution if it supports
    // if you need to upload to the server, but server does't support to upload by streaming,
    // you can compress the resolution to lower. or you can support more higher resolution.
    //开始压缩视频
    //现在我们压缩到低分辨率是否支持
    //如果需要上传服务器,但由流媒体服务器不支持上传,
    //可以压缩分辨率降低。或者你可以支持更多的高分辨率。
    if ([presets containsobject:avassetexportpreset640x480]) {
        avassetexportsession * session = [[avassetexportsession alloc]initwithasset:videoasset presetname:avassetexportpreset640x480];
        //nshomedirectory()  得到的是应用程序目录的路径,在该目录下有三个文件夹:documents、library、temp以及一个.app包!该目录下就是应用程序的沙盒,应用程序只能访问该目录下的文件夹!!!
        nsstring * doc = [nshomedirectory() stringbyappendingpathcomponent:@"documents"];
        
        nsstring * folder = [doc stringbyappendingpathcomponent:@"录制视频"];
        bool isdir = no;
        bool isexist = [[nsfilemanager defaultmanager] fileexistsatpath:folder isdirectory:&isdir];
        if (!isexist || (isexist && !isdir)) {
            
            nserror * error = nil;
            
            [[nsfilemanager defaultmanager]createdirectoryatpath:folder withintermediatedirectories:yes attributes:nil error:&error];
            if (error == nil) {
                [svprogresshud showinfowithstatus:@"目录创建成功"];
            } else {
                [svprogresshud showinfowithstatus:@"目录创建失败"];
                
            }
        }
        
        nsstring * outputpath = [folder stringbyappendingpathcomponent:savedname];
        session.outputurl = [nsurl fileurlwithpath:outputpath];
        // optimize for network use.
        session.shouldoptimizefornetworkuse = true;
        
        nsarray * supportedtypearray = session.supportedfiletypes;
        if ([supportedtypearray containsobject:avfiletypempeg4]) {
            session.outputfiletype = avfiletypempeg4;
            
        } else if (supportedtypearray.count == 0){
            
            [svprogresshud showinfowithstatus:@"no supported file types"];
            return;
            
        } else {
            
            session.outputfiletype = [supportedtypearray objectatindex:0];
            
        }
        
        // begin to export video to the output path asynchronously.
        //开始出口异步视频输出路径。
        [session exportasynchronouslywithcompletionhandler:^{
            
            if ([session status] == avassetexportsessionstatuscompleted) {
                
                dispatch_async(dispatch_get_main_queue(), ^{
                    
                    if (completion) {
                        
                        completion([session.outputurl path
                                    ]);
                        
                    }
                    
                });
                
            } else {
                
                dispatch_async(dispatch_get_main_queue(), ^{
                    
                    if (completion) {
                        completion(nil);
                    }
                    
                });
                
            }
            
        }];
        
    }
}

- (void)didreceivememorywarning {
    [super didreceivememorywarning];
    // dispose of any resources that can be recreated.
}
#pragma mark - 相关属性参数
/**
 *  (1)代理
 delegate
 
 (2)几个基本属性设置
 sourcetype  //设置资源获取类型
 allowsediting   //是否允许图片编辑
 
 (3)几个判断类方法
 *是否可以获取该类型资源
+ (bool)issourcetypeavailable:(uiimagepickercontrollersourcetype)sourcetype;

*是否可以获取该类型相机(前置和后置 )
+ (bool)iscameradeviceavailable:(uiimagepickercontrollercameradevice)cameradevice;

*是否可以获取闪光灯
+ (bool)isflashavailableforcameradevice:(uiimagepickercontrollercameradevice)cameradevice;

(4)代理方法(ios4后仅存2个可用)
- (void)imagepickercontroller:(uiimagepickercontroller *)picker didfinishpickingmediawithinfo:(nsdictionary *)info;
- (void)imagepickercontrollerdidcancel:(uiimagepickercontroller *)picker;
* 参数info中的键
nsstring *const  uiimagepickercontrollermediatype ;指定用户选择的媒体类型(文章最后进行扩展)
nsstring *const  uiimagepickercontrolleroriginalimage ;原始图片
nsstring *const  uiimagepickercontrollereditedimage ;修改后的图片
nsstring *const  uiimagepickercontrollercroprect ;裁剪尺寸
nsstring *const  uiimagepickercontrollermediaurl ;媒体的url
nsstring *const  uiimagepickercontrollerreferenceurl ;原件的url
nsstring *const  uiimagepickercontrollermediametadata;当来数据来源是照相机的时候这个值才有效

* uiimagepickercontrollermediatype
uiimagepickercontrollermediatype 包含着kuttypeimage 和kuttypemovie
kuttypeimage 包含:
const cfstringref  kuttypeimage ;抽象的图片类型
const cfstringref  kuttypejpeg ;
const cfstringref  kuttypejpeg2000 ;
const cfstringref  kuttypetiff ;
const cfstringref  kuttypepict ;
const cfstringref  kuttypegif ;
const cfstringref  kuttypepng ;
const cfstringref  kuttypequicktimeimage ;
const cfstringref  kuttypeappleicns
const cfstringref kuttypebmp;
const cfstringref  kuttypeico;

kuttypemovie 包含:
const cfstringref  kuttypeaudiovisualcontent ;抽象的声音视频
const cfstringref  kuttypemovie ;抽象的媒体格式(声音和视频)
const cfstringref  kuttypevideo ;只有视频没有声音
const cfstringref  kuttypeaudio ;只有声音没有视频
const cfstringref  kuttypequicktimemovie ;
const cfstringref  kuttypempeg ;
const cfstringref  kuttypempeg4 ;
const cfstringref  kuttypemp3 ;
const cfstringref  kuttypempeg4audio ;
const cfstringref  kuttypeappleprotectedmpeg4audio;
 */

@end