iOS 录像功能的简单实现
程序员文章站
2023-11-21 20:15:10
话不多说,上 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