公司项目,需要拍照片和拍摄视频,并添加水印。今天这里就先整理一下视频和照片的拍摄,水印另外讲解。
基于模块化思维,拍摄视频相关的方法、功能封装到唯一类中,这个类怎么设计,不用多讲,网上相关介绍很多。这里就讲解功能实现。
基本配置
我们通过AVFoundation
框架来实现图片的获取和视频的获取
//捕获设备,通常是前置摄像头,后置摄像头,麦克风(音频输入)
@property (nonatomic ,strong)AVCaptureDevice *device;
//AVCaptureDeviceInput 代表输入设备,他使用AVCaptureDevice 来初始化
@property (nonatomic ,strong)AVCaptureDeviceInput *input;
//当启动摄像头开始捕获输入
@property (nonatomic ,strong)AVCaptureMetadataOutput *output;
//视频文件的输出
@property (nonatomic ,strong)AVCaptureMovieFileOutput *movieOutput;
//照片输出流
@property (nonatomic ,strong)AVCaptureStillImageOutput *ImageOutPut;
////图像预览层,实时显示捕获的图像
@property (nonatomic)AVCaptureVideoPreviewLayer *previewLayer;
获取图片的预览使用的是previewLayer
一个图层,需要添加到目标控制器view的layer上。
//使用AVMediaTypeVideo 指明self.device代表视频,默认使用后置摄像头进行初始化
self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//使用AVMediaTypeAudio,代表着音频(麦克风),如果需要音频可以不用
AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
//初始化输入设备
self.input = [[AVCaptureDeviceInput alloc]initWithDevice:self.device error:nil];
AVCaptureDeviceInput *audioInput = [[AVCaptureDeviceInput alloc]initWithDevice:audioDevice error:nil];
//如果需要拍摄视频加上👇一句
self.movieOutput = [[AVCaptureMovieFileOutput alloc]init];
//self.movieOutput.maxRecordedDuration = maxDuration;//拍摄时长,可以限定也可以无限
//self.movieOutput.minFreeDiskSpaceLimit = 10*1024*1024;//视频最小大小限制,如果限制了,会自动结束视频拍摄
//图片的输出
self.ImageOutPut = [[AVCaptureStillImageOutput alloc]init];
//生产多媒体回话session,通过会话链接输入、输出设备
self.session = [[AVCaptureSession alloc]init];
if ([self.session canSetSessionPreset:AVCaptureSessionPreset1920x1080]) {//设置清晰度
[self.session setSessionPreset:AVCaptureSessionPreset1920x1080];
}
//先判断是否可以添加设备,再执行add
if ([self.session canAddInput:self.input]) {
[self.session addInput:self.input];
}
if ([self.session canAddInput:audioInput]) {
[self.session addInput:audioInput];
}
if ([self.session canAddOutput:self.ImageOutPut]) {
[self.session addOutput:self.ImageOutPut];
}
if ([self.session canAddOutput:self.movieOutput]) {
[self.session addOutput:self.movieOutput];
}
//视频预览图层
self.previewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.session];
[self.previewLayer setBackgroundColor:[UIColor blackColor].CGColor];
[self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[self.previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight];
CGRect layerFrame = CGRectMake(0, 0, kScreenHeight, kScreenWidth);
[self.previewLayer setFrame:layerFrame];
[self.homeVc.view.layer insertSublayer:self.previewLayer atIndex:0];
//修改设备的属性,先加锁
if ([self.device lockForConfiguration:nil]) {
//自动白平衡
if ([self.device isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeAutoWhiteBalance]) {
[self.device setWhiteBalanceMode:AVCaptureWhiteBalanceModeAutoWhiteBalance];
}
//解锁
[self.device unlockForConfiguration];
}
//开始启动做好拍照和录视频的准备,这个时候就可以在目标控制器上看到镜头采集的数据了
[self.session startRunning];
设备方向配置
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(orientationDidChangeNotification:)
name:UIDeviceOrientationDidChangeNotification
object:nil]
//
if([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft){
[self.previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight];
}else if([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeRight){
[self.previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeLeft];
}else{
}
拍照
AVCaptureConnection * videoConnection = [self.ImageOutPut connectionWithMediaType:AVMediaTypeVideo];
if (videoConnection == nil) {//视频的IO是否正常
return;
}
AVCaptureVideoOrientation avcaptureOrientation = AVCaptureVideoOrientationLandscapeRight;
if([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft){
avcaptureOrientation = AVCaptureVideoOrientationLandscapeRight;
}else if([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeRight){
avcaptureOrientation = AVCaptureVideoOrientationLandscapeLeft;
}
[videoConnection setVideoOrientation:avcaptureOrientation];
[videoConnection setVideoScaleAndCropFactor:1];
//获取当前帧图片
[self.ImageOutPut captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
if (imageDataSampleBuffer == nil) {
return;
}
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
//获取当前相机的方向(前还是后)
AVCaptureDevicePosition position = [[self.input device] position];
UIImage *image = [UIImage imageWithData:imageData];
if (position == AVCaptureDevicePositionFront) {
UIImageOrientation flipImageOrientation = (image.imageOrientation + 3) % 8;
finalImg = [UIImage imageWithCGImage: image.CGImage scale: image.scale orientation:flipImageOrientation];
}
水印
UIImage *img = [UIImage imageNamed:@"ic_muyuan_logo"];//水印
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();//创建颜色
//Bitmap上下文
CGContextRef context = CGBitmapContextCreate(NULL, image.size.width, image.size.height, 8, 44 * image.size.width, colorSpace, kCGImageAlphaPremultipliedFirst);
CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);//将image绘至context上下文中
CGContextDrawImage(context, CGRectMake(image.size.width - 200, image.size.height - 150, 150, 47), img.CGImage);//将img绘至context上下文中
// CGContextSetRGBFillColor(context, 0.0, 1.0, 1.0, 1);//设置颜色
//Create image ref from the context
CGImageRef imageMasked = CGBitmapContextCreateImage(context);//创建CGImage
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
UIImage *finalImg = image;
finalImg = [UIImage imageWithCGImage:imageMasked];//加盖水印的图片
拍摄视频
视频的拍摄需要有开始和结束的操作
1、开始拍摄,需要设定视频文件的临时存放地址
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSURL *documentDirUrl = [NSURL fileURLWithPath:path isDirectory:YES];
NSURL *fileURL = [NSURL URLWithString:@"movieOut.mp4" relativeToURL:documentDirUrl];
//开启视频拍摄,拍摄的过程中出错和拍摄完成都在delegate中
[self.movieOutput startRecordingToOutputFileURL:fileURL recordingDelegate:self];
2、拍摄过程:
[self.movieOutput stopRecording];完成拍摄
[self.movieOutpu pauseRecording]暂停
[self.movieOutpu resumeRecording]重启拍摄,可以更新文件地址
3、拍摄过程中delegate
//暂停录制
- (void)captureOutput:(AVCaptureFileOutput *)output didPauseRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray<AVCaptureConnection *> *)connections{
NSLog(@" fileURL is %@",fileURL.path);
if (self.recordAction) {
self.recordAction(fileURL, NSError.new);
}
}
//结束录制、不论是主动完成还是被动停止,只要拍摄停止都会调用这个方法
- (void)captureOutput:(AVCaptureFileOutput *)output didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray<AVCaptureConnection *> *)connections error:(NSError *)error{
NSLog(@" outputFileURL is %@",outputFileURL.path);
if (self.recordAction) {
self.recordAction(outputFileURL, error);
}
}
长按圆环动画
环形动画很简单,就是两个CAShapeLayer,一个当作背景,一个在画弧度。
backCircleLayer = [CAShapeLayer layer];
backCircleLayer.frame=self.bounds;
backCircleLayer.fillColor=nil;
progressLayer = [CAShapeLayer layer];
progressLayer.frame = self.bounds;
progressLayer.lineCap = kCALineCapRound;
[self.layer addSublayer:backCircleLayer];
[self.layer addSublayer:progressLayer];
backCirclelayer的宽度、颜色、都可以作为属性暴露在接口中,让用户自己定义
-(void)setLineWidth:(CGFloat)lineWidth{
_lineWidth = lineWidth;
backCircleLayer.lineWidth = lineWidth;
progressLayer.lineWidth = lineWidth;
//
UIBezierPath * backpath = [UIBezierPath bezierPathWithArcCenter:self.center radius:(CGRectGetWidth(self.bounds)-lineWidth)/2.f startAngle:0 endAngle:M_PI*2
clockwise:YES];
backCircleLayer.path = backpath.CGPath;
}
-(void)setStrokeColor:(UIColor *)strokeColor{
_strokeColor = strokeColor;
if (_strokeColor) {
backCircleLayer.strokeColor = strokeColor.CGColor;
}
}
-(void)setProgressColor:(UIColor *)progressColor{
_progressColor = progressColor;
if (_progressColor) {
progressLayer.strokeColor = progressColor.CGColor;
}
}
动画的过程,需要通过计时器来累加绘制。👇初始化动画开始的初始弧度和弧度变化步长
preAngle = preInterval/self.duration * 2*M_PI;
startAngle = -M_PI/2;
if (!self.clockwise) {
startAngle = 3 * M_PI/2;
preAngle = -preAngle;
}
动画的开始、变化、结束的过程比较简单就不细说了
-(void)start{
if (_lineWidth<=0) {
NSLog(@"进度条宽度需大于0");
return;
}
if (_duration<=0) {
NSLog(@"动画时间需大于0");
return;
}
if (self.timer) {
[self.timer invalidate];
self.timer = nil;
}
stopTimer = NO;
self.timer = [NSTimer scheduledTimerWithTimeInterval:preInterval target:self selector:@selector(changeProgress) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
-(void)changeProgress{
if (stopTimer) {
return;
}
UIBezierPath *progressPath = [UIBezierPath bezierPathWithArcCenter:self.center radius:(CGRectGetWidth(self.bounds)-_lineWidth)/2.f startAngle: startAngle endAngle:startAngle+preAngle clockwise:self.clockwise];
progressLayer.path = progressPath.CGPath;
NSLog(@"curent Angle is %f",startAngle);
startAngle = startAngle+preAngle;
if ( fabs(startAngle - (self.clockwise? -M_PI/2:3*M_PI/2))<1e-6 ) {//判断两个Double值是否相等
stopTimer = YES;
[self.timer invalidate];
self.timer = nil;
}
}
-(void)end{
stopTimer = YES;
if (self.timer) {
[self.timer invalidate];
self.timer = nil;
CGFloat tempflt =self.clockwise? -M_PI/2:3*M_PI/2;
UIBezierPath *progressPath = [UIBezierPath bezierPathWithArcCenter:self.center radius:(CGRectGetWidth(self.bounds)-_lineWidth)/2.f startAngle:tempflt endAngle:tempflt clockwise:self.clockwise];
progressLayer.path = progressPath.CGPath;
}
}
视频预览和使用
上面讲述了拍照、拍摄视频、进度圆环动画等实现的过程,还缺少最后一点儿,就是怎么预览拍摄的视频,这就需要一个视频播放器,如果只是简单的使用拍摄到的视频,那么使用AVPlayerViewController,就可以展示拍摄的视频。
如果要使用视频,或者要对视频进行二次加工,比如美颜、添加水印、或者添加音乐等等。这就需要在视频播放器上自定义功能按钮,这个时候就需要自定义视频播放器,微信就是这样。
如何创建自定义的视频播放器,网上教程很多,我这里就简单的讲解一下过程步骤(AVPlayer)
1、把网络和本地视频转化为NSURL
,加载到播放器
AVPlayerItem *playItem = [AVPlayerItem playerItemWithURL:url];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.frame = view.bounds;
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;//视频填充模式
[view.layer addSublayer:playerLayer];
至于播放、暂停、快进等自定义的控制按钮,需要用户自己在view
上展示,视频播放状态需要通过KVO或者NotificationCenter,自己处理。