From 60dd115ae9de1528e8c3f7c0bf2deb9e58b24732 Mon Sep 17 00:00:00 2001 From: yangshengchaoios Date: Wed, 8 Jan 2014 10:58:07 +0800 Subject: [PATCH 1/3] add two demo class --- ...213\347\232\204\346\223\215\344\275\234.m" | 522 ++++++++++++++++++ ...207\347\224\250\344\272\206CameraEngine.m" | 241 ++++++++ 2 files changed, 763 insertions(+) create mode 100755 "\346\234\200\345\216\237\345\247\213\347\232\204\346\223\215\344\275\234.m" create mode 100755 "\351\207\207\347\224\250\344\272\206CameraEngine.m" diff --git "a/\346\234\200\345\216\237\345\247\213\347\232\204\346\223\215\344\275\234.m" "b/\346\234\200\345\216\237\345\247\213\347\232\204\346\223\215\344\275\234.m" new file mode 100755 index 0000000..227644e --- /dev/null +++ "b/\346\234\200\345\216\237\345\247\213\347\232\204\346\223\215\344\275\234.m" @@ -0,0 +1,522 @@ +// +// VideoRecordViewController.m +// Jasmine +// +// Created by 杨胜超 on 13-12-31. +// Copyright (c) 2013年 Huanrun. All rights reserved. +// + +#import "VideoRecordViewController.h" + +#define FrameDuration 24.0 //设置每秒多少帧 +#define MaxVideoSeconds 15 //设置最多几秒 +#define MinVideoSeconds 5 //设置最少几秒 + +#define VideoWidth 640.0f +#define VideoHeight 640.0f + +static CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;}; + +@interface VideoRecordViewController () + +@property (nonatomic, weak) IBOutlet UIProgressView* progressBar; +@property (nonatomic, weak) IBOutlet UIImageView *topTipImageView; + +@property (nonatomic, weak) IBOutlet UIView *cameraView; +@property (nonatomic, weak) IBOutlet UIImageView *leftTipImageView; +@property (nonatomic, weak) IBOutlet UIImageView *rightTipImageView; +@property (nonatomic, weak) IBOutlet UIImageView *bottomTipImageView; + +@property (nonatomic, weak) IBOutlet UIView *bottomContainerView; +@property (nonatomic, weak) IBOutlet UIButton *cancelButton; //取消录像 +@property (nonatomic, weak) IBOutlet UIButton *nextStepButton; //下一步 +@property (nonatomic, weak) IBOutlet UIButton *reversalButton; //翻转 + +@property (nonatomic, retain) AVAssetWriter *assetWriter; +@property (nonatomic, retain) AVAssetWriterInput *assetWriterInput; +@property (nonatomic, strong) AVCaptureDevice *videoDevice; +@property (nonatomic, strong) AVCaptureDevice *audioDevice; +@property (nonatomic, strong) AVCaptureSession *session; +@property (nonatomic, strong) AVCaptureDeviceInput *captureVideoInput; +@property (nonatomic, strong) AVCaptureDeviceInput *captureAudioInput; +@property (nonatomic, strong) AVCaptureVideoDataOutput *captureVideoOutput; +@property (nonatomic, strong) AVCaptureAudioDataOutput *captureAudioOutput; +@property (nonatomic, strong) AVCaptureVideoPreviewLayer *preview; + +@property (nonatomic, strong) UILongPressGestureRecognizer *longPressGesture; + +@property (nonatomic, retain) NSURL *outputMovURL; +@property (nonatomic, retain) NSURL* outputMp4URL; +@property (nonatomic, assign) BOOL isStarted; +@property (nonatomic, assign) CMTime frameDuration; +@property (nonatomic, assign) CMTime nextPTS; +@property (nonatomic, assign) NSInteger currentFrame; //当前第几帧 +@property (nonatomic, assign) NSInteger maxFrame; //最大多少帧 +@property (nonatomic, assign) NSInteger minFrame; //最少多少帧 +@property (nonatomic, assign) double duration; //录制时长 + +@property (nonatomic, assign) BOOL isLongScreen; + +@end + +@implementation VideoRecordViewController + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; +} + +- (void)dealloc { + [self.session stopRunning]; + self.session = nil; + self.videoDevice = nil; + self.captureVideoInput = nil; + + self.audioDevice = nil; + self.captureAudioInput = nil; + + self.assetWriter = nil; + self.assetWriterInput = nil; + + self.captureVideoOutput = nil; + self.outputMovURL = nil; + self.outputMp4URL = nil; + self.cameraView = nil; + self.preview = nil; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self initSubviews]; + [self initCamera]; + + [self.cameraView bringSubviewToFront:self.bottomTipImageView]; + [self.cameraView bringSubviewToFront:self.leftTipImageView]; + [self.cameraView bringSubviewToFront:self.rightTipImageView]; +} + +- (BOOL)shouldAutorotate { + return YES; +} +- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { + HRLOG(@"toInterfaceOrientation = %d", toInterfaceOrientation); +} + +- (void)initSubviews { + self.isStarted = NO; + self.topBarHidden = YES; + self.backButton.hidden = YES; + self.progressBar.progress = 0; + self.nextStepButton.hidden = YES; + self.view.backgroundColor = [UIColor blackColor]; + self.isLongScreen = [UIScreen mainScreen].bounds.size.height > 500; + self.leftTipImageView.hidden = self.rightTipImageView.hidden = YES; + self.topTipImageView.hidden = self.bottomTipImageView.hidden = NO; + self.leftTipImageView.image = [UIImage imageNamed:@"video_left_labdscape_tip"]; + self.rightTipImageView.image = [UIImage imageNamed:@"video_right_labdscape_tip"]; + self.topTipImageView.image = [UIImage imageNamed:@"video_tip01"]; + WeakSelfType blockSelf = self; + + //设置按钮点击事件 + [self.cancelButton addTarget:self action:@selector(cancelButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; + [self.nextStepButton addTarget:self action:@selector(nextStepButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; + [self.reversalButton addTarget:self action:@selector(reversalButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; + + //设置缓存视频路径 + self.outputMovURL = [NSURL fileURLWithPath:[[[StorageManager sharedInstance] tmpDirectoryPath] stringByAppendingPathComponent:@"postvideo.mov"]]; + self.outputMp4URL = [NSURL fileURLWithPath:[[[StorageManager sharedInstance] tmpDirectoryPath] stringByAppendingPathComponent:@"postvideo.mp4"]]; + + //设置每秒24帧 + self.frameDuration = CMTimeMakeWithSeconds(1.0 / FrameDuration, 90000); + self.currentFrame = 0; + self.maxFrame = FrameDuration * MaxVideoSeconds; + self.minFrame = FrameDuration * MinVideoSeconds; + + //根据长短屏幕调整UI + if (self.isLongScreen) {//长屏幕 TODO:需要统一判断 + self.cameraView.frame = CGRectSetY(self.cameraView.frame, 5 + CGRectGetMaxY(self.topTipImageView.frame)); + } + else {//短屏幕 + self.cameraView.frame = CGRectSetY(self.cameraView.frame, self.topTipImageView.frame.origin.y); + } + + //设置长按手势 + self.longPressGesture = [[UILongPressGestureRecognizer alloc] initWithHandler:^(UIGestureRecognizer *sender, UIGestureRecognizerState state, CGPoint location) { + blockSelf.bottomTipImageView.hidden = YES; + + if (state == UIGestureRecognizerStateBegan) { + if (blockSelf.currentFrame == 0) {//开始录制 + HRLOG(@"开始录制..."); + blockSelf.isStarted = YES; + [blockSelf deleteFile:[blockSelf.outputMp4URL path]]; + [blockSelf deleteFile:[blockSelf.outputMovURL path]]; + blockSelf.topTipImageView.image = [UIImage imageNamed:@"video_tip02"]; + } + else {//继续录制 + HRLOG(@"继续录制"); + blockSelf.isStarted = YES; + blockSelf.topTipImageView.image = [UIImage imageNamed:@"video_tip02"]; + } + } + else if (state == UIGestureRecognizerStateEnded) { + HRLOG(@"暂停"); + blockSelf.isStarted = NO; + blockSelf.topTipImageView.image = [UIImage imageNamed:@"video_tip03"]; + } + }]; + [self.cameraView addGestureRecognizer:self.longPressGesture]; + + //注册监听设备的方向属性 + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onDeviceOrientationChange) name:UIDeviceOrientationDidChangeNotification object:nil]; +} + +-(void)onDeviceOrientationChange { + UIDeviceOrientation orientation = [UIDevice currentDevice].orientation; + switch (orientation) { + case 3: + self.rightTipImageView.hidden = NO; + self.topTipImageView.hidden = self.leftTipImageView.hidden = YES; + break; + case 4: + self.leftTipImageView.hidden = NO; + self.topTipImageView.hidden = self.rightTipImageView.hidden = YES; + break; + default: + self.topTipImageView.hidden = NO; + self.leftTipImageView.hidden = self.rightTipImageView.hidden = YES; + break; + } +} + +- (void)initCamera { + NSError *error; + + //1.创建会话层 + self.session = [[AVCaptureSession alloc] init]; + [self.session setSessionPreset:AVCaptureSessionPresetiFrame960x540]; + [self.session beginConfiguration]; + + //2.创建设备配置视频输入 + self.videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + [self.videoDevice lockForConfiguration:nil]; + [self.videoDevice unlockForConfiguration]; + self.captureVideoInput = [AVCaptureDeviceInput deviceInputWithDevice:self.videoDevice error:&error]; + if ( ! self.captureVideoInput){ + HRLOG(@"Error: %@", error); + return; + } + [self.session addInput:self.captureVideoInput]; + + //3.创建设备配置音频输入 + self.audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; + [self.audioDevice lockForConfiguration:nil]; + [self.audioDevice unlockForConfiguration]; + self.captureAudioInput = [AVCaptureDeviceInput deviceInputWithDevice:self.audioDevice error:&error]; + if ( ! self.captureAudioInput){ + HRLOG(@"Error: %@", error); + return; + } + [self.session addInput:self.captureAudioInput]; + + //4.配置视频输出 + self.captureVideoOutput = [[AVCaptureVideoDataOutput alloc] init]; + [self.captureVideoOutput setAlwaysDiscardsLateVideoFrames:YES]; + self.captureVideoOutput.videoSettings = @{(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)};//录像设置 + [self.session addOutput:self.captureVideoOutput]; + [self.captureVideoOutput setSampleBufferDelegate:self queue:dispatch_queue_create("myQueue", NULL)]; + + //5.配置视频输出 + self.captureAudioOutput = [[AVCaptureAudioDataOutput alloc] init]; + [self.session addOutput:self.captureAudioOutput]; + + //6.创建显示层 + self.preview = [AVCaptureVideoPreviewLayer layerWithSession: self.session]; + self.preview.frame = CGRectMake(0, 0, self.cameraView.frame.size.width, self.cameraView.frame.size.height); + self.preview.videoGravity = AVLayerVideoGravityResizeAspectFill; + + [self.cameraView.layer addSublayer:self.preview]; + [self.session commitConfiguration]; + [self.session startRunning]; +} + +#pragma mark - ButtonClickedEvent + +- (IBAction)cancelButtonClicked:(id)sender { + WeakSelfType blockSelf = self; + if (self.isStarted) { + [UIAlertView showAlertViewWithTitle:@"取消录制" + message:@"您已经录制了一段视频了,确定要放弃?" + cancelButtonTitle:@"按错了" + otherButtonTitles:@[@"确定放弃"] + handler:^(UIAlertView *alertView, NSInteger buttonIndex) { + if (buttonIndex == 1) { + if ([blockSelf.delegate respondsToSelector:@selector(videoRecordViewControllerDidCancel:)]) { + [blockSelf.delegate videoRecordViewControllerDidCancel:blockSelf]; + } + } + }]; + } + else { + if ([self.delegate respondsToSelector:@selector(videoRecordViewControllerDidCancel:)]) { + [self.delegate videoRecordViewControllerDidCancel:self]; + } + } +} + +- (IBAction)nextStepButtonClicked:(id)sender { + WeakSelfType blockSelf = self; + self.isStarted = NO; + //取消camera上的长按手势 + [self.cameraView removeGestureRecognizer:self.longPressGesture]; + //计算时长 + self.duration = self.currentFrame * 1.0f / FrameDuration; + + if (self.assetWriter) { + [self showHUDLoadingWithString:@"正在保存"]; + [self.assetWriterInput markAsFinished]; + [self.assetWriter finishWritingWithCompletionHandler:^{ + [blockSelf convertToMp4]; + }]; + } +} + +- (void)convertToMp4 { + WeakSelfType blockSelf = self; + NSString* _mp4Quality = AVAssetExportPresetMediumQuality; + + // 试图删除原mp4 + [self deleteFile:[self.outputMp4URL path]]; + + // 生成mp4 + AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:self.outputMovURL options:nil]; + NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset]; + + if ([compatiblePresets containsObject:_mp4Quality]) { + __block AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]initWithAsset:avAsset + presetName:_mp4Quality]; + + exportSession.outputURL = self.outputMp4URL; + exportSession.outputFileType = AVFileTypeMPEG4; + [exportSession exportAsynchronouslyWithCompletionHandler:^{ + [blockSelf hideHUDLoading]; + switch ([exportSession status]) { + case AVAssetExportSessionStatusFailed: + [blockSelf showResultThenHide:@"转换mp4出错"]; + break; + case AVAssetExportSessionStatusCancelled: + [blockSelf showResultThenHide:@"转换被取消"]; + break; + case AVAssetExportSessionStatusCompleted: + [blockSelf performSelectorOnMainThread:@selector(convertFinish) withObject:nil waitUntilDone:NO]; + break; + default: + break; + } + }]; + } + else { + [self hideHUDLoading]; + [self showResultThenHide:@"转换mp4出错!"]; + } +} + +- (void)convertFinish { + [self deleteFile:[self.outputMovURL path]]; + if ([self.delegate respondsToSelector:@selector(videoRecordViewController:didFinishVideoRecordWithVideoPath:andVideoDuration:)]) { + [self.delegate videoRecordViewController:self didFinishVideoRecordWithVideoPath:[self.outputMp4URL path] andVideoDuration:self.duration]; + } +} + +- (IBAction)reversalButtonClicked:(id)sender { + [self flipFromCALayer:self.preview]; + + NSArray *inputs = self.session.inputs; + for ( AVCaptureDeviceInput *input in inputs ) { + AVCaptureDevice *device = input.device; + if ([device hasMediaType:AVMediaTypeVideo]) { + AVCaptureDevicePosition position = device.position; + AVCaptureDevice *newCamera = nil; + AVCaptureDeviceInput *newInput = nil; + + if (position == AVCaptureDevicePositionFront) { + newCamera = [self cameraWithPosition:AVCaptureDevicePositionBack]; + } + else { + newCamera = [self cameraWithPosition:AVCaptureDevicePositionFront]; + } + self.videoDevice = newCamera; + newInput = [AVCaptureDeviceInput deviceInputWithDevice:newCamera error:nil]; + + // beginConfiguration ensures that pending changes are not applied immediately + [self.session beginConfiguration]; + + [self.session removeInput:input]; + [self.session addInput:newInput]; + + // Changes take effect once the outermost commitConfiguration is invoked. + [self.session commitConfiguration]; + break; + } + } +} + +#pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate + +// Delegate routine that is called when a sample buffer was written +- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { + if (self.isStarted) { + // set up the AVAssetWriter using the format description from the first sample buffer captured + if ( self.assetWriter == nil ) { + //NSLog(@"Writing movie to \"%@\"", outputURL); + CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer); + if ( NO == [self setupAssetWriterForURL:self.outputMovURL formatDescription:formatDescription] ) { + return; + } + } + // re-time the sample buffer - in this sample frameDuration is set to 5 fps + CMSampleTimingInfo timingInfo = kCMTimingInfoInvalid; + timingInfo.duration = self.frameDuration; + timingInfo.presentationTimeStamp = self.nextPTS; + CMSampleBufferRef sbufWithNewTiming = NULL; + + + OSStatus err = CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, + sampleBuffer, + 1, // numSampleTimingEntries + &timingInfo, + &sbufWithNewTiming); + if (err) { + HRLOG(@"CMSampleBufferCreateCopyWithNewTiming error"); + return; + } + + // append the sample buffer if we can and increment presnetation time + if ( [self.assetWriterInput isReadyForMoreMediaData] ) { + if ([self.assetWriterInput appendSampleBuffer:sbufWithNewTiming]) { + self.nextPTS = CMTimeAdd(self.frameDuration, self.nextPTS); + } + else { + NSError *error = [self.assetWriter error]; + HRLOG(@"failed to append sbuf: %@", error); + } + } + else { + HRLOG(@"isReadyForMoreMediaData error"); + } + + // release the copy of the sample buffer we made + CFRelease(sbufWithNewTiming); + + self.currentFrame ++; //累加一帧 + dispatch_async(dispatch_get_main_queue(), ^{ + CGFloat p = (CGFloat)((CGFloat)self.currentFrame / (CGFloat)self.maxFrame); + [self.progressBar setProgress:p animated:YES]; + + if (self.currentFrame >= self.minFrame && self.nextStepButton.hidden) { + self.nextStepButton.hidden = NO; + self.topTipImageView.image = [UIImage imageNamed:@"video_tip04"]; + } + if (self.currentFrame >= self.maxFrame) { + self.topTipImageView.hidden = YES; + [self nextStepButtonClicked:nil]; + } + }); + } +} + +#pragma mark - private method + +- (void)flipFromCALayer:(CALayer *)layer { + CATransition *animation = [CATransition animation]; + animation.delegate = self; + animation.duration = 0.2f; + animation.timingFunction = UIViewAnimationCurveEaseInOut; + animation.type = @"flip"; + if (self.videoDevice.position == AVCaptureDevicePositionFront) { + animation.subtype = kCATransitionFromRight; + } + else if(self.videoDevice.position == AVCaptureDevicePositionBack) { + animation.subtype = kCATransitionFromLeft; + } + [layer addAnimation:animation forKey:@"Transition1"]; +} + +- (void)deleteFile:(NSString *) filePath { + if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; + } +} + +//切换前后摄像头 +- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position{ + NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; + for (AVCaptureDevice *device in devices) + { + if (device.position == position) + { + return device; + } + } + return nil; +} + +- (BOOL)setupAssetWriterForURL:(NSURL *)fileURL formatDescription:(CMFormatDescriptionRef)formatDescription { + NSError *error = nil; + self.assetWriter = [[AVAssetWriter alloc] initWithURL:fileURL fileType:AVFileTypeQuickTimeMovie error:&error]; + if (error) + return NO; + + // initialized a new input for video to receive sample buffers for writing + // passing nil for outputSettings instructs the input to pass through appended samples, doing no processing before they are written + // 下面这个参数,设置图像质量,数字越大,质量越好 + NSDictionary *videoCompressionProps = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithDouble:512*1024.0], AVVideoAverageBitRateKey, + nil ]; + // 设置编码和宽高比。宽高比最好和摄像比例一致,否则图片可能被压缩或拉伸 + NSDictionary* dic = [NSDictionary dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey, + [NSNumber numberWithFloat:VideoWidth], AVVideoWidthKey, + [NSNumber numberWithFloat:VideoHeight], AVVideoHeightKey, + AVVideoScalingModeResizeAspectFill, AVVideoScalingModeKey, + videoCompressionProps, AVVideoCompressionPropertiesKey, nil]; + self.assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:dic]; + [self.assetWriterInput setExpectsMediaDataInRealTime:YES]; + if ([self.assetWriter canAddInput:self.assetWriterInput]) + [self.assetWriter addInput:self.assetWriterInput]; + + // specify the prefered transform for the output file + CGFloat rotationDegrees; + switch ([[UIDevice currentDevice] orientation]) { + case UIDeviceOrientationPortraitUpsideDown: + rotationDegrees = -90.; + break; + case UIDeviceOrientationLandscapeLeft: // no rotation + rotationDegrees = 0.; + break; + case UIDeviceOrientationLandscapeRight: + rotationDegrees = 180.; + break; + case UIDeviceOrientationPortrait: + case UIDeviceOrientationUnknown: + case UIDeviceOrientationFaceUp: + case UIDeviceOrientationFaceDown: + default: + rotationDegrees = 90.; + break; + } + CGFloat rotationRadians = DegreesToRadians(rotationDegrees); + [self.assetWriterInput setTransform:CGAffineTransformMakeRotation(rotationRadians)]; + + // initiates a sample-writing at time 0 + self.nextPTS = kCMTimeZero; + [self.assetWriter startWriting]; + [self.assetWriter startSessionAtSourceTime:self.nextPTS]; + + return YES; +} + +@end diff --git "a/\351\207\207\347\224\250\344\272\206CameraEngine.m" "b/\351\207\207\347\224\250\344\272\206CameraEngine.m" new file mode 100755 index 0000000..38a3882 --- /dev/null +++ "b/\351\207\207\347\224\250\344\272\206CameraEngine.m" @@ -0,0 +1,241 @@ +// +// VideoRecordViewController.m +// Jasmine +// +// Created by 杨胜超 on 13-12-31. +// Copyright (c) 2013年 Huanrun. All rights reserved. +// + +#import "VideoRecordViewController.h" +#import "CameraEngine.h" + +#define MaxVideoSeconds 15.0f //设置最多几秒 +#define MinVideoSeconds 5.0f //设置最少几秒 + +static CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;}; + +@interface VideoRecordViewController () + +@property (nonatomic, weak) IBOutlet UIProgressView* progressBar; +@property (nonatomic, weak) IBOutlet UIImageView *topTipImageView; + +@property (nonatomic, weak) IBOutlet UIView *cameraView; +@property (nonatomic, weak) IBOutlet UIImageView *leftTipImageView; +@property (nonatomic, weak) IBOutlet UIImageView *rightTipImageView; +@property (nonatomic, weak) IBOutlet UIImageView *bottomTipImageView; + +@property (nonatomic, weak) IBOutlet UIView *bottomContainerView; +@property (nonatomic, weak) IBOutlet UIButton *cancelButton; //取消录像 +@property (nonatomic, weak) IBOutlet UIButton *nextStepButton; //下一步 +@property (nonatomic, weak) IBOutlet UIButton *reversalButton; //翻转 + +@property (nonatomic, strong) AVCaptureVideoPreviewLayer *preview; +@property (nonatomic, strong) UILongPressGestureRecognizer *longPressGesture; +@property (nonatomic, strong) NSTimer *timer; + +@property (nonatomic, assign) NSTimeInterval startInterval; +@property (nonatomic, assign) double duration; //录制时长 +@property (nonatomic, assign) BOOL isLongScreen; + +@end + +@implementation VideoRecordViewController + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + // Custom initialization + } + return self; +} + +- (void)viewWillDisappear:(BOOL)animated { + [[CameraEngine engine] shutdown]; + [super viewWillDisappear:animated]; +} + +- (void)viewDidLoad { + HRLOG(@"in viewdidload"); + [super viewDidLoad]; + [[CameraEngine engine] startup]; + + [self initSubviews]; + + [self.cameraView bringSubviewToFront:self.bottomTipImageView]; + [self.cameraView bringSubviewToFront:self.leftTipImageView]; + [self.cameraView bringSubviewToFront:self.rightTipImageView]; + HRLOG(@"finish viewdidload"); +} + +- (BOOL)shouldAutorotate { + return YES; +} +- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { + HRLOG(@"toInterfaceOrientation = %d", toInterfaceOrientation); +} + +- (void)initSubviews { + HRLOG(@"start init subviews"); + self.duration = 0; + self.topBarHidden = YES; + self.backButton.hidden = YES; + self.progressBar.progress = 0; + self.nextStepButton.hidden = YES; + self.view.backgroundColor = [UIColor blackColor]; + self.isLongScreen = [UIScreen mainScreen].bounds.size.height > 500; + self.leftTipImageView.alpha = self.rightTipImageView.alpha = 0; + self.topTipImageView.alpha = self.bottomTipImageView.alpha = 1; + self.leftTipImageView.image = [UIImage imageNamed:@"video_left_labdscape_tip"]; + self.rightTipImageView.image = [UIImage imageNamed:@"video_right_labdscape_tip"]; + self.topTipImageView.image = [UIImage imageNamed:@"video_tip01"]; + WeakSelfType blockSelf = self; + + //根据长短屏幕调整UI + if (self.isLongScreen) {//长屏幕 TODO:需要统一判断 + self.cameraView.frame = CGRectSetY(self.cameraView.frame, 5 + CGRectGetMaxY(self.topTipImageView.frame)); + } + else {//短屏幕 + self.cameraView.frame = CGRectSetY(self.cameraView.frame, self.topTipImageView.frame.origin.y); + } + HRLOG(@"display previewlayer"); + //设置显示层 + AVCaptureVideoPreviewLayer* preview = [[CameraEngine engine] getPreviewLayer]; + [preview removeFromSuperlayer]; + preview.frame = self.cameraView.bounds; + [self.cameraView.layer addSublayer:preview]; + + HRLOG(@"startup camera engine"); + //设置按钮点击事件 + [self.cancelButton addTarget:self action:@selector(cancelButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; + [self.nextStepButton addTarget:self action:@selector(nextStepButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; + [self.reversalButton addTarget:self action:@selector(reversalButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; + + //设置长按手势 + self.longPressGesture = [[UILongPressGestureRecognizer alloc] initWithHandler:^(UIGestureRecognizer *sender, UIGestureRecognizerState state, CGPoint location) { + [blockSelf displayImageView:blockSelf.bottomTipImageView withAlpha:0]; + + if (state == UIGestureRecognizerStateBegan) { + if (blockSelf.duration == 0) {//开始录制 + HRLOG(@"开始录制..."); + blockSelf.topTipImageView.image = [UIImage imageNamed:@"video_tip02"]; + [[CameraEngine engine] startCapture]; + blockSelf.startInterval = [NSDate date].timeIntervalSince1970; + } + else {//继续录制 + HRLOG(@"继续录制"); + blockSelf.topTipImageView.image = [UIImage imageNamed:@"video_tip02"]; + [[CameraEngine engine] resumeCapture]; + blockSelf.startInterval = [NSDate date].timeIntervalSince1970; + } + } + else if (state == UIGestureRecognizerStateEnded) { + HRLOG(@"暂停"); + blockSelf.topTipImageView.image = [UIImage imageNamed:@"video_tip03"]; + [[CameraEngine engine] pauseCapture]; + blockSelf.duration += [NSDate date].timeIntervalSince1970 - blockSelf.startInterval;//累计时长 + HRLOG(@"duration = %f", blockSelf.duration); + } + }]; + [self.cameraView addGestureRecognizer:self.longPressGesture]; + + //注册监听设备的方向属性 + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onDeviceOrientationChange) name:UIDeviceOrientationDidChangeNotification object:nil]; + + //设置定时器 + self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1f + block:^(NSTimer *timer) { + if ([CameraEngine engine].isCapturing && ! [CameraEngine engine].isPaused) { + dispatch_async(dispatch_get_main_queue(), ^{ + double currentDuration = [NSDate date].timeIntervalSince1970 - blockSelf.startInterval; + double totalDuration = currentDuration + blockSelf.duration; + HRLOG(@"totalDuration = %f", totalDuration); + //1. 更新进度条 + [blockSelf.progressBar setProgress:totalDuration / MaxVideoSeconds animated:YES]; + //2. 达到最少秒数才显示【下一步】按钮 + if (totalDuration >= MinVideoSeconds && blockSelf.nextStepButton.hidden) { + blockSelf.nextStepButton.hidden = NO; + blockSelf.topTipImageView.image = [UIImage imageNamed:@"video_tip04"]; + } + //3. 达到最大秒数,自动停止拍摄 + if (totalDuration >= MaxVideoSeconds) { + blockSelf.topTipImageView.hidden = YES; + blockSelf.duration = totalDuration; + [blockSelf nextStepButtonClicked:nil]; + } + }); + } + } + repeats:YES]; + HRLOG(@"finish init subviews"); +} + +-(void)onDeviceOrientationChange { + UIDeviceOrientation orientation = [UIDevice currentDevice].orientation; + switch (orientation) { + case 3: + [self displayImageView:self.rightTipImageView withAlpha:1]; + [self displayImageView:self.topTipImageView withAlpha:0]; + [self displayImageView:self.leftTipImageView withAlpha:0]; + break; + case 4: + [self displayImageView:self.leftTipImageView withAlpha:1]; + [self displayImageView:self.topTipImageView withAlpha:0]; + [self displayImageView:self.rightTipImageView withAlpha:0]; + break; + default: + [self displayImageView:self.topTipImageView withAlpha:1]; + [self displayImageView:self.leftTipImageView withAlpha:0]; + [self displayImageView:self.rightTipImageView withAlpha:0]; + break; + } +} + +- (void)displayImageView:(UIImageView *)imageView withAlpha:(float) alpha { + [UIView animateWithDuration:0.3f + animations:^{ + imageView.alpha = alpha; + }]; +} + +#pragma mark - ButtonClickedEvent + +- (IBAction)cancelButtonClicked:(id)sender { + WeakSelfType blockSelf = self; + if (self.duration > 0) { + [UIAlertView showAlertViewWithTitle:@"取消录制" + message:@"您已经录制了一段视频了,确定要放弃?" + cancelButtonTitle:@"按错了" + otherButtonTitles:@[@"确定放弃"] + handler:^(UIAlertView *alertView, NSInteger buttonIndex) { + if (buttonIndex == 1) { + if ([blockSelf.delegate respondsToSelector:@selector(videoRecordViewControllerDidCancel:)]) { + [blockSelf.delegate videoRecordViewControllerDidCancel:blockSelf]; + } + } + }]; + } + else { + if ([self.delegate respondsToSelector:@selector(videoRecordViewControllerDidCancel:)]) { + [self.delegate videoRecordViewControllerDidCancel:self]; + } + } +} + +- (IBAction)nextStepButtonClicked:(id)sender { + //1. 取消camera上的长按手势 + [self.cameraView removeGestureRecognizer:self.longPressGesture]; + //2. 停止拍摄 + [[CameraEngine engine] stopCapture]; + HRLOG(@"录制总时长:%f", self.duration); + //3. 回调 + if ([self.delegate respondsToSelector:@selector(videoRecordViewController:didFinishVideoRecordWithVideoPath:andVideoDuration:)]) { + [self.delegate videoRecordViewController:self didFinishVideoRecordWithVideoPath:[[CameraEngine engine].outputMp4URL path] andVideoDuration:self.duration]; + } +} + +- (void)reversalButtonClicked:(id)sender { + [[CameraEngine engine] reversalCapture]; +} + +@end From 69b03f8ba92a617008a6f5fa2b09a2fa242f30f0 Mon Sep 17 00:00:00 2001 From: yangshengchaoios Date: Wed, 15 Jan 2014 09:45:58 +0800 Subject: [PATCH 2/3] add new gitignore file, delete two test file --- .gitignore | 12 +- .../contents.xcworkspacedata | 7 + ...213\347\232\204\346\223\215\344\275\234.m" | 522 ------------------ ...207\347\224\250\344\272\206CameraEngine.m" | 241 -------- 4 files changed, 16 insertions(+), 766 deletions(-) create mode 100644 MultiVidCam.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100755 "\346\234\200\345\216\237\345\247\213\347\232\204\346\223\215\344\275\234.m" delete mode 100755 "\351\207\207\347\224\250\344\272\206CameraEngine.m" diff --git a/.gitignore b/.gitignore index edc082c..6e13704 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Xcode -build/* +.DS_Store +*/build/* *.pbxuser !default.pbxuser *.mode1v3 @@ -8,8 +9,13 @@ build/* !default.mode2v3 *.perspectivev3 !default.perspectivev3 -*.xcworkspace -!default.xcworkspace xcuserdata profile *.moved-aside +DerivedData +.idea/ +*.hmap +*.xccheckout + +#CocoaPods +Pods diff --git a/MultiVidCam.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/MultiVidCam.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..77bc7ff --- /dev/null +++ b/MultiVidCam.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git "a/\346\234\200\345\216\237\345\247\213\347\232\204\346\223\215\344\275\234.m" "b/\346\234\200\345\216\237\345\247\213\347\232\204\346\223\215\344\275\234.m" deleted file mode 100755 index 227644e..0000000 --- "a/\346\234\200\345\216\237\345\247\213\347\232\204\346\223\215\344\275\234.m" +++ /dev/null @@ -1,522 +0,0 @@ -// -// VideoRecordViewController.m -// Jasmine -// -// Created by 杨胜超 on 13-12-31. -// Copyright (c) 2013年 Huanrun. All rights reserved. -// - -#import "VideoRecordViewController.h" - -#define FrameDuration 24.0 //设置每秒多少帧 -#define MaxVideoSeconds 15 //设置最多几秒 -#define MinVideoSeconds 5 //设置最少几秒 - -#define VideoWidth 640.0f -#define VideoHeight 640.0f - -static CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;}; - -@interface VideoRecordViewController () - -@property (nonatomic, weak) IBOutlet UIProgressView* progressBar; -@property (nonatomic, weak) IBOutlet UIImageView *topTipImageView; - -@property (nonatomic, weak) IBOutlet UIView *cameraView; -@property (nonatomic, weak) IBOutlet UIImageView *leftTipImageView; -@property (nonatomic, weak) IBOutlet UIImageView *rightTipImageView; -@property (nonatomic, weak) IBOutlet UIImageView *bottomTipImageView; - -@property (nonatomic, weak) IBOutlet UIView *bottomContainerView; -@property (nonatomic, weak) IBOutlet UIButton *cancelButton; //取消录像 -@property (nonatomic, weak) IBOutlet UIButton *nextStepButton; //下一步 -@property (nonatomic, weak) IBOutlet UIButton *reversalButton; //翻转 - -@property (nonatomic, retain) AVAssetWriter *assetWriter; -@property (nonatomic, retain) AVAssetWriterInput *assetWriterInput; -@property (nonatomic, strong) AVCaptureDevice *videoDevice; -@property (nonatomic, strong) AVCaptureDevice *audioDevice; -@property (nonatomic, strong) AVCaptureSession *session; -@property (nonatomic, strong) AVCaptureDeviceInput *captureVideoInput; -@property (nonatomic, strong) AVCaptureDeviceInput *captureAudioInput; -@property (nonatomic, strong) AVCaptureVideoDataOutput *captureVideoOutput; -@property (nonatomic, strong) AVCaptureAudioDataOutput *captureAudioOutput; -@property (nonatomic, strong) AVCaptureVideoPreviewLayer *preview; - -@property (nonatomic, strong) UILongPressGestureRecognizer *longPressGesture; - -@property (nonatomic, retain) NSURL *outputMovURL; -@property (nonatomic, retain) NSURL* outputMp4URL; -@property (nonatomic, assign) BOOL isStarted; -@property (nonatomic, assign) CMTime frameDuration; -@property (nonatomic, assign) CMTime nextPTS; -@property (nonatomic, assign) NSInteger currentFrame; //当前第几帧 -@property (nonatomic, assign) NSInteger maxFrame; //最大多少帧 -@property (nonatomic, assign) NSInteger minFrame; //最少多少帧 -@property (nonatomic, assign) double duration; //录制时长 - -@property (nonatomic, assign) BOOL isLongScreen; - -@end - -@implementation VideoRecordViewController - -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - if (self) { - // Custom initialization - } - return self; -} - -- (void)dealloc { - [self.session stopRunning]; - self.session = nil; - self.videoDevice = nil; - self.captureVideoInput = nil; - - self.audioDevice = nil; - self.captureAudioInput = nil; - - self.assetWriter = nil; - self.assetWriterInput = nil; - - self.captureVideoOutput = nil; - self.outputMovURL = nil; - self.outputMp4URL = nil; - self.cameraView = nil; - self.preview = nil; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - [self initSubviews]; - [self initCamera]; - - [self.cameraView bringSubviewToFront:self.bottomTipImageView]; - [self.cameraView bringSubviewToFront:self.leftTipImageView]; - [self.cameraView bringSubviewToFront:self.rightTipImageView]; -} - -- (BOOL)shouldAutorotate { - return YES; -} -- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { - HRLOG(@"toInterfaceOrientation = %d", toInterfaceOrientation); -} - -- (void)initSubviews { - self.isStarted = NO; - self.topBarHidden = YES; - self.backButton.hidden = YES; - self.progressBar.progress = 0; - self.nextStepButton.hidden = YES; - self.view.backgroundColor = [UIColor blackColor]; - self.isLongScreen = [UIScreen mainScreen].bounds.size.height > 500; - self.leftTipImageView.hidden = self.rightTipImageView.hidden = YES; - self.topTipImageView.hidden = self.bottomTipImageView.hidden = NO; - self.leftTipImageView.image = [UIImage imageNamed:@"video_left_labdscape_tip"]; - self.rightTipImageView.image = [UIImage imageNamed:@"video_right_labdscape_tip"]; - self.topTipImageView.image = [UIImage imageNamed:@"video_tip01"]; - WeakSelfType blockSelf = self; - - //设置按钮点击事件 - [self.cancelButton addTarget:self action:@selector(cancelButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; - [self.nextStepButton addTarget:self action:@selector(nextStepButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; - [self.reversalButton addTarget:self action:@selector(reversalButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; - - //设置缓存视频路径 - self.outputMovURL = [NSURL fileURLWithPath:[[[StorageManager sharedInstance] tmpDirectoryPath] stringByAppendingPathComponent:@"postvideo.mov"]]; - self.outputMp4URL = [NSURL fileURLWithPath:[[[StorageManager sharedInstance] tmpDirectoryPath] stringByAppendingPathComponent:@"postvideo.mp4"]]; - - //设置每秒24帧 - self.frameDuration = CMTimeMakeWithSeconds(1.0 / FrameDuration, 90000); - self.currentFrame = 0; - self.maxFrame = FrameDuration * MaxVideoSeconds; - self.minFrame = FrameDuration * MinVideoSeconds; - - //根据长短屏幕调整UI - if (self.isLongScreen) {//长屏幕 TODO:需要统一判断 - self.cameraView.frame = CGRectSetY(self.cameraView.frame, 5 + CGRectGetMaxY(self.topTipImageView.frame)); - } - else {//短屏幕 - self.cameraView.frame = CGRectSetY(self.cameraView.frame, self.topTipImageView.frame.origin.y); - } - - //设置长按手势 - self.longPressGesture = [[UILongPressGestureRecognizer alloc] initWithHandler:^(UIGestureRecognizer *sender, UIGestureRecognizerState state, CGPoint location) { - blockSelf.bottomTipImageView.hidden = YES; - - if (state == UIGestureRecognizerStateBegan) { - if (blockSelf.currentFrame == 0) {//开始录制 - HRLOG(@"开始录制..."); - blockSelf.isStarted = YES; - [blockSelf deleteFile:[blockSelf.outputMp4URL path]]; - [blockSelf deleteFile:[blockSelf.outputMovURL path]]; - blockSelf.topTipImageView.image = [UIImage imageNamed:@"video_tip02"]; - } - else {//继续录制 - HRLOG(@"继续录制"); - blockSelf.isStarted = YES; - blockSelf.topTipImageView.image = [UIImage imageNamed:@"video_tip02"]; - } - } - else if (state == UIGestureRecognizerStateEnded) { - HRLOG(@"暂停"); - blockSelf.isStarted = NO; - blockSelf.topTipImageView.image = [UIImage imageNamed:@"video_tip03"]; - } - }]; - [self.cameraView addGestureRecognizer:self.longPressGesture]; - - //注册监听设备的方向属性 - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onDeviceOrientationChange) name:UIDeviceOrientationDidChangeNotification object:nil]; -} - --(void)onDeviceOrientationChange { - UIDeviceOrientation orientation = [UIDevice currentDevice].orientation; - switch (orientation) { - case 3: - self.rightTipImageView.hidden = NO; - self.topTipImageView.hidden = self.leftTipImageView.hidden = YES; - break; - case 4: - self.leftTipImageView.hidden = NO; - self.topTipImageView.hidden = self.rightTipImageView.hidden = YES; - break; - default: - self.topTipImageView.hidden = NO; - self.leftTipImageView.hidden = self.rightTipImageView.hidden = YES; - break; - } -} - -- (void)initCamera { - NSError *error; - - //1.创建会话层 - self.session = [[AVCaptureSession alloc] init]; - [self.session setSessionPreset:AVCaptureSessionPresetiFrame960x540]; - [self.session beginConfiguration]; - - //2.创建设备配置视频输入 - self.videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; - [self.videoDevice lockForConfiguration:nil]; - [self.videoDevice unlockForConfiguration]; - self.captureVideoInput = [AVCaptureDeviceInput deviceInputWithDevice:self.videoDevice error:&error]; - if ( ! self.captureVideoInput){ - HRLOG(@"Error: %@", error); - return; - } - [self.session addInput:self.captureVideoInput]; - - //3.创建设备配置音频输入 - self.audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; - [self.audioDevice lockForConfiguration:nil]; - [self.audioDevice unlockForConfiguration]; - self.captureAudioInput = [AVCaptureDeviceInput deviceInputWithDevice:self.audioDevice error:&error]; - if ( ! self.captureAudioInput){ - HRLOG(@"Error: %@", error); - return; - } - [self.session addInput:self.captureAudioInput]; - - //4.配置视频输出 - self.captureVideoOutput = [[AVCaptureVideoDataOutput alloc] init]; - [self.captureVideoOutput setAlwaysDiscardsLateVideoFrames:YES]; - self.captureVideoOutput.videoSettings = @{(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)};//录像设置 - [self.session addOutput:self.captureVideoOutput]; - [self.captureVideoOutput setSampleBufferDelegate:self queue:dispatch_queue_create("myQueue", NULL)]; - - //5.配置视频输出 - self.captureAudioOutput = [[AVCaptureAudioDataOutput alloc] init]; - [self.session addOutput:self.captureAudioOutput]; - - //6.创建显示层 - self.preview = [AVCaptureVideoPreviewLayer layerWithSession: self.session]; - self.preview.frame = CGRectMake(0, 0, self.cameraView.frame.size.width, self.cameraView.frame.size.height); - self.preview.videoGravity = AVLayerVideoGravityResizeAspectFill; - - [self.cameraView.layer addSublayer:self.preview]; - [self.session commitConfiguration]; - [self.session startRunning]; -} - -#pragma mark - ButtonClickedEvent - -- (IBAction)cancelButtonClicked:(id)sender { - WeakSelfType blockSelf = self; - if (self.isStarted) { - [UIAlertView showAlertViewWithTitle:@"取消录制" - message:@"您已经录制了一段视频了,确定要放弃?" - cancelButtonTitle:@"按错了" - otherButtonTitles:@[@"确定放弃"] - handler:^(UIAlertView *alertView, NSInteger buttonIndex) { - if (buttonIndex == 1) { - if ([blockSelf.delegate respondsToSelector:@selector(videoRecordViewControllerDidCancel:)]) { - [blockSelf.delegate videoRecordViewControllerDidCancel:blockSelf]; - } - } - }]; - } - else { - if ([self.delegate respondsToSelector:@selector(videoRecordViewControllerDidCancel:)]) { - [self.delegate videoRecordViewControllerDidCancel:self]; - } - } -} - -- (IBAction)nextStepButtonClicked:(id)sender { - WeakSelfType blockSelf = self; - self.isStarted = NO; - //取消camera上的长按手势 - [self.cameraView removeGestureRecognizer:self.longPressGesture]; - //计算时长 - self.duration = self.currentFrame * 1.0f / FrameDuration; - - if (self.assetWriter) { - [self showHUDLoadingWithString:@"正在保存"]; - [self.assetWriterInput markAsFinished]; - [self.assetWriter finishWritingWithCompletionHandler:^{ - [blockSelf convertToMp4]; - }]; - } -} - -- (void)convertToMp4 { - WeakSelfType blockSelf = self; - NSString* _mp4Quality = AVAssetExportPresetMediumQuality; - - // 试图删除原mp4 - [self deleteFile:[self.outputMp4URL path]]; - - // 生成mp4 - AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:self.outputMovURL options:nil]; - NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset]; - - if ([compatiblePresets containsObject:_mp4Quality]) { - __block AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]initWithAsset:avAsset - presetName:_mp4Quality]; - - exportSession.outputURL = self.outputMp4URL; - exportSession.outputFileType = AVFileTypeMPEG4; - [exportSession exportAsynchronouslyWithCompletionHandler:^{ - [blockSelf hideHUDLoading]; - switch ([exportSession status]) { - case AVAssetExportSessionStatusFailed: - [blockSelf showResultThenHide:@"转换mp4出错"]; - break; - case AVAssetExportSessionStatusCancelled: - [blockSelf showResultThenHide:@"转换被取消"]; - break; - case AVAssetExportSessionStatusCompleted: - [blockSelf performSelectorOnMainThread:@selector(convertFinish) withObject:nil waitUntilDone:NO]; - break; - default: - break; - } - }]; - } - else { - [self hideHUDLoading]; - [self showResultThenHide:@"转换mp4出错!"]; - } -} - -- (void)convertFinish { - [self deleteFile:[self.outputMovURL path]]; - if ([self.delegate respondsToSelector:@selector(videoRecordViewController:didFinishVideoRecordWithVideoPath:andVideoDuration:)]) { - [self.delegate videoRecordViewController:self didFinishVideoRecordWithVideoPath:[self.outputMp4URL path] andVideoDuration:self.duration]; - } -} - -- (IBAction)reversalButtonClicked:(id)sender { - [self flipFromCALayer:self.preview]; - - NSArray *inputs = self.session.inputs; - for ( AVCaptureDeviceInput *input in inputs ) { - AVCaptureDevice *device = input.device; - if ([device hasMediaType:AVMediaTypeVideo]) { - AVCaptureDevicePosition position = device.position; - AVCaptureDevice *newCamera = nil; - AVCaptureDeviceInput *newInput = nil; - - if (position == AVCaptureDevicePositionFront) { - newCamera = [self cameraWithPosition:AVCaptureDevicePositionBack]; - } - else { - newCamera = [self cameraWithPosition:AVCaptureDevicePositionFront]; - } - self.videoDevice = newCamera; - newInput = [AVCaptureDeviceInput deviceInputWithDevice:newCamera error:nil]; - - // beginConfiguration ensures that pending changes are not applied immediately - [self.session beginConfiguration]; - - [self.session removeInput:input]; - [self.session addInput:newInput]; - - // Changes take effect once the outermost commitConfiguration is invoked. - [self.session commitConfiguration]; - break; - } - } -} - -#pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate - -// Delegate routine that is called when a sample buffer was written -- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { - if (self.isStarted) { - // set up the AVAssetWriter using the format description from the first sample buffer captured - if ( self.assetWriter == nil ) { - //NSLog(@"Writing movie to \"%@\"", outputURL); - CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer); - if ( NO == [self setupAssetWriterForURL:self.outputMovURL formatDescription:formatDescription] ) { - return; - } - } - // re-time the sample buffer - in this sample frameDuration is set to 5 fps - CMSampleTimingInfo timingInfo = kCMTimingInfoInvalid; - timingInfo.duration = self.frameDuration; - timingInfo.presentationTimeStamp = self.nextPTS; - CMSampleBufferRef sbufWithNewTiming = NULL; - - - OSStatus err = CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, - sampleBuffer, - 1, // numSampleTimingEntries - &timingInfo, - &sbufWithNewTiming); - if (err) { - HRLOG(@"CMSampleBufferCreateCopyWithNewTiming error"); - return; - } - - // append the sample buffer if we can and increment presnetation time - if ( [self.assetWriterInput isReadyForMoreMediaData] ) { - if ([self.assetWriterInput appendSampleBuffer:sbufWithNewTiming]) { - self.nextPTS = CMTimeAdd(self.frameDuration, self.nextPTS); - } - else { - NSError *error = [self.assetWriter error]; - HRLOG(@"failed to append sbuf: %@", error); - } - } - else { - HRLOG(@"isReadyForMoreMediaData error"); - } - - // release the copy of the sample buffer we made - CFRelease(sbufWithNewTiming); - - self.currentFrame ++; //累加一帧 - dispatch_async(dispatch_get_main_queue(), ^{ - CGFloat p = (CGFloat)((CGFloat)self.currentFrame / (CGFloat)self.maxFrame); - [self.progressBar setProgress:p animated:YES]; - - if (self.currentFrame >= self.minFrame && self.nextStepButton.hidden) { - self.nextStepButton.hidden = NO; - self.topTipImageView.image = [UIImage imageNamed:@"video_tip04"]; - } - if (self.currentFrame >= self.maxFrame) { - self.topTipImageView.hidden = YES; - [self nextStepButtonClicked:nil]; - } - }); - } -} - -#pragma mark - private method - -- (void)flipFromCALayer:(CALayer *)layer { - CATransition *animation = [CATransition animation]; - animation.delegate = self; - animation.duration = 0.2f; - animation.timingFunction = UIViewAnimationCurveEaseInOut; - animation.type = @"flip"; - if (self.videoDevice.position == AVCaptureDevicePositionFront) { - animation.subtype = kCATransitionFromRight; - } - else if(self.videoDevice.position == AVCaptureDevicePositionBack) { - animation.subtype = kCATransitionFromLeft; - } - [layer addAnimation:animation forKey:@"Transition1"]; -} - -- (void)deleteFile:(NSString *) filePath { - if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { - [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; - } -} - -//切换前后摄像头 -- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position{ - NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; - for (AVCaptureDevice *device in devices) - { - if (device.position == position) - { - return device; - } - } - return nil; -} - -- (BOOL)setupAssetWriterForURL:(NSURL *)fileURL formatDescription:(CMFormatDescriptionRef)formatDescription { - NSError *error = nil; - self.assetWriter = [[AVAssetWriter alloc] initWithURL:fileURL fileType:AVFileTypeQuickTimeMovie error:&error]; - if (error) - return NO; - - // initialized a new input for video to receive sample buffers for writing - // passing nil for outputSettings instructs the input to pass through appended samples, doing no processing before they are written - // 下面这个参数,设置图像质量,数字越大,质量越好 - NSDictionary *videoCompressionProps = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithDouble:512*1024.0], AVVideoAverageBitRateKey, - nil ]; - // 设置编码和宽高比。宽高比最好和摄像比例一致,否则图片可能被压缩或拉伸 - NSDictionary* dic = [NSDictionary dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey, - [NSNumber numberWithFloat:VideoWidth], AVVideoWidthKey, - [NSNumber numberWithFloat:VideoHeight], AVVideoHeightKey, - AVVideoScalingModeResizeAspectFill, AVVideoScalingModeKey, - videoCompressionProps, AVVideoCompressionPropertiesKey, nil]; - self.assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:dic]; - [self.assetWriterInput setExpectsMediaDataInRealTime:YES]; - if ([self.assetWriter canAddInput:self.assetWriterInput]) - [self.assetWriter addInput:self.assetWriterInput]; - - // specify the prefered transform for the output file - CGFloat rotationDegrees; - switch ([[UIDevice currentDevice] orientation]) { - case UIDeviceOrientationPortraitUpsideDown: - rotationDegrees = -90.; - break; - case UIDeviceOrientationLandscapeLeft: // no rotation - rotationDegrees = 0.; - break; - case UIDeviceOrientationLandscapeRight: - rotationDegrees = 180.; - break; - case UIDeviceOrientationPortrait: - case UIDeviceOrientationUnknown: - case UIDeviceOrientationFaceUp: - case UIDeviceOrientationFaceDown: - default: - rotationDegrees = 90.; - break; - } - CGFloat rotationRadians = DegreesToRadians(rotationDegrees); - [self.assetWriterInput setTransform:CGAffineTransformMakeRotation(rotationRadians)]; - - // initiates a sample-writing at time 0 - self.nextPTS = kCMTimeZero; - [self.assetWriter startWriting]; - [self.assetWriter startSessionAtSourceTime:self.nextPTS]; - - return YES; -} - -@end diff --git "a/\351\207\207\347\224\250\344\272\206CameraEngine.m" "b/\351\207\207\347\224\250\344\272\206CameraEngine.m" deleted file mode 100755 index 38a3882..0000000 --- "a/\351\207\207\347\224\250\344\272\206CameraEngine.m" +++ /dev/null @@ -1,241 +0,0 @@ -// -// VideoRecordViewController.m -// Jasmine -// -// Created by 杨胜超 on 13-12-31. -// Copyright (c) 2013年 Huanrun. All rights reserved. -// - -#import "VideoRecordViewController.h" -#import "CameraEngine.h" - -#define MaxVideoSeconds 15.0f //设置最多几秒 -#define MinVideoSeconds 5.0f //设置最少几秒 - -static CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;}; - -@interface VideoRecordViewController () - -@property (nonatomic, weak) IBOutlet UIProgressView* progressBar; -@property (nonatomic, weak) IBOutlet UIImageView *topTipImageView; - -@property (nonatomic, weak) IBOutlet UIView *cameraView; -@property (nonatomic, weak) IBOutlet UIImageView *leftTipImageView; -@property (nonatomic, weak) IBOutlet UIImageView *rightTipImageView; -@property (nonatomic, weak) IBOutlet UIImageView *bottomTipImageView; - -@property (nonatomic, weak) IBOutlet UIView *bottomContainerView; -@property (nonatomic, weak) IBOutlet UIButton *cancelButton; //取消录像 -@property (nonatomic, weak) IBOutlet UIButton *nextStepButton; //下一步 -@property (nonatomic, weak) IBOutlet UIButton *reversalButton; //翻转 - -@property (nonatomic, strong) AVCaptureVideoPreviewLayer *preview; -@property (nonatomic, strong) UILongPressGestureRecognizer *longPressGesture; -@property (nonatomic, strong) NSTimer *timer; - -@property (nonatomic, assign) NSTimeInterval startInterval; -@property (nonatomic, assign) double duration; //录制时长 -@property (nonatomic, assign) BOOL isLongScreen; - -@end - -@implementation VideoRecordViewController - -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - if (self) { - // Custom initialization - } - return self; -} - -- (void)viewWillDisappear:(BOOL)animated { - [[CameraEngine engine] shutdown]; - [super viewWillDisappear:animated]; -} - -- (void)viewDidLoad { - HRLOG(@"in viewdidload"); - [super viewDidLoad]; - [[CameraEngine engine] startup]; - - [self initSubviews]; - - [self.cameraView bringSubviewToFront:self.bottomTipImageView]; - [self.cameraView bringSubviewToFront:self.leftTipImageView]; - [self.cameraView bringSubviewToFront:self.rightTipImageView]; - HRLOG(@"finish viewdidload"); -} - -- (BOOL)shouldAutorotate { - return YES; -} -- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { - HRLOG(@"toInterfaceOrientation = %d", toInterfaceOrientation); -} - -- (void)initSubviews { - HRLOG(@"start init subviews"); - self.duration = 0; - self.topBarHidden = YES; - self.backButton.hidden = YES; - self.progressBar.progress = 0; - self.nextStepButton.hidden = YES; - self.view.backgroundColor = [UIColor blackColor]; - self.isLongScreen = [UIScreen mainScreen].bounds.size.height > 500; - self.leftTipImageView.alpha = self.rightTipImageView.alpha = 0; - self.topTipImageView.alpha = self.bottomTipImageView.alpha = 1; - self.leftTipImageView.image = [UIImage imageNamed:@"video_left_labdscape_tip"]; - self.rightTipImageView.image = [UIImage imageNamed:@"video_right_labdscape_tip"]; - self.topTipImageView.image = [UIImage imageNamed:@"video_tip01"]; - WeakSelfType blockSelf = self; - - //根据长短屏幕调整UI - if (self.isLongScreen) {//长屏幕 TODO:需要统一判断 - self.cameraView.frame = CGRectSetY(self.cameraView.frame, 5 + CGRectGetMaxY(self.topTipImageView.frame)); - } - else {//短屏幕 - self.cameraView.frame = CGRectSetY(self.cameraView.frame, self.topTipImageView.frame.origin.y); - } - HRLOG(@"display previewlayer"); - //设置显示层 - AVCaptureVideoPreviewLayer* preview = [[CameraEngine engine] getPreviewLayer]; - [preview removeFromSuperlayer]; - preview.frame = self.cameraView.bounds; - [self.cameraView.layer addSublayer:preview]; - - HRLOG(@"startup camera engine"); - //设置按钮点击事件 - [self.cancelButton addTarget:self action:@selector(cancelButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; - [self.nextStepButton addTarget:self action:@selector(nextStepButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; - [self.reversalButton addTarget:self action:@selector(reversalButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; - - //设置长按手势 - self.longPressGesture = [[UILongPressGestureRecognizer alloc] initWithHandler:^(UIGestureRecognizer *sender, UIGestureRecognizerState state, CGPoint location) { - [blockSelf displayImageView:blockSelf.bottomTipImageView withAlpha:0]; - - if (state == UIGestureRecognizerStateBegan) { - if (blockSelf.duration == 0) {//开始录制 - HRLOG(@"开始录制..."); - blockSelf.topTipImageView.image = [UIImage imageNamed:@"video_tip02"]; - [[CameraEngine engine] startCapture]; - blockSelf.startInterval = [NSDate date].timeIntervalSince1970; - } - else {//继续录制 - HRLOG(@"继续录制"); - blockSelf.topTipImageView.image = [UIImage imageNamed:@"video_tip02"]; - [[CameraEngine engine] resumeCapture]; - blockSelf.startInterval = [NSDate date].timeIntervalSince1970; - } - } - else if (state == UIGestureRecognizerStateEnded) { - HRLOG(@"暂停"); - blockSelf.topTipImageView.image = [UIImage imageNamed:@"video_tip03"]; - [[CameraEngine engine] pauseCapture]; - blockSelf.duration += [NSDate date].timeIntervalSince1970 - blockSelf.startInterval;//累计时长 - HRLOG(@"duration = %f", blockSelf.duration); - } - }]; - [self.cameraView addGestureRecognizer:self.longPressGesture]; - - //注册监听设备的方向属性 - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onDeviceOrientationChange) name:UIDeviceOrientationDidChangeNotification object:nil]; - - //设置定时器 - self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1f - block:^(NSTimer *timer) { - if ([CameraEngine engine].isCapturing && ! [CameraEngine engine].isPaused) { - dispatch_async(dispatch_get_main_queue(), ^{ - double currentDuration = [NSDate date].timeIntervalSince1970 - blockSelf.startInterval; - double totalDuration = currentDuration + blockSelf.duration; - HRLOG(@"totalDuration = %f", totalDuration); - //1. 更新进度条 - [blockSelf.progressBar setProgress:totalDuration / MaxVideoSeconds animated:YES]; - //2. 达到最少秒数才显示【下一步】按钮 - if (totalDuration >= MinVideoSeconds && blockSelf.nextStepButton.hidden) { - blockSelf.nextStepButton.hidden = NO; - blockSelf.topTipImageView.image = [UIImage imageNamed:@"video_tip04"]; - } - //3. 达到最大秒数,自动停止拍摄 - if (totalDuration >= MaxVideoSeconds) { - blockSelf.topTipImageView.hidden = YES; - blockSelf.duration = totalDuration; - [blockSelf nextStepButtonClicked:nil]; - } - }); - } - } - repeats:YES]; - HRLOG(@"finish init subviews"); -} - --(void)onDeviceOrientationChange { - UIDeviceOrientation orientation = [UIDevice currentDevice].orientation; - switch (orientation) { - case 3: - [self displayImageView:self.rightTipImageView withAlpha:1]; - [self displayImageView:self.topTipImageView withAlpha:0]; - [self displayImageView:self.leftTipImageView withAlpha:0]; - break; - case 4: - [self displayImageView:self.leftTipImageView withAlpha:1]; - [self displayImageView:self.topTipImageView withAlpha:0]; - [self displayImageView:self.rightTipImageView withAlpha:0]; - break; - default: - [self displayImageView:self.topTipImageView withAlpha:1]; - [self displayImageView:self.leftTipImageView withAlpha:0]; - [self displayImageView:self.rightTipImageView withAlpha:0]; - break; - } -} - -- (void)displayImageView:(UIImageView *)imageView withAlpha:(float) alpha { - [UIView animateWithDuration:0.3f - animations:^{ - imageView.alpha = alpha; - }]; -} - -#pragma mark - ButtonClickedEvent - -- (IBAction)cancelButtonClicked:(id)sender { - WeakSelfType blockSelf = self; - if (self.duration > 0) { - [UIAlertView showAlertViewWithTitle:@"取消录制" - message:@"您已经录制了一段视频了,确定要放弃?" - cancelButtonTitle:@"按错了" - otherButtonTitles:@[@"确定放弃"] - handler:^(UIAlertView *alertView, NSInteger buttonIndex) { - if (buttonIndex == 1) { - if ([blockSelf.delegate respondsToSelector:@selector(videoRecordViewControllerDidCancel:)]) { - [blockSelf.delegate videoRecordViewControllerDidCancel:blockSelf]; - } - } - }]; - } - else { - if ([self.delegate respondsToSelector:@selector(videoRecordViewControllerDidCancel:)]) { - [self.delegate videoRecordViewControllerDidCancel:self]; - } - } -} - -- (IBAction)nextStepButtonClicked:(id)sender { - //1. 取消camera上的长按手势 - [self.cameraView removeGestureRecognizer:self.longPressGesture]; - //2. 停止拍摄 - [[CameraEngine engine] stopCapture]; - HRLOG(@"录制总时长:%f", self.duration); - //3. 回调 - if ([self.delegate respondsToSelector:@selector(videoRecordViewController:didFinishVideoRecordWithVideoPath:andVideoDuration:)]) { - [self.delegate videoRecordViewController:self didFinishVideoRecordWithVideoPath:[[CameraEngine engine].outputMp4URL path] andVideoDuration:self.duration]; - } -} - -- (void)reversalButtonClicked:(id)sender { - [[CameraEngine engine] reversalCapture]; -} - -@end From 318e7ddf621532739ee23e79870cca7132ea774f Mon Sep 17 00:00:00 2001 From: yangshengchaoios Date: Thu, 16 Jan 2014 15:26:16 +0800 Subject: [PATCH 3/3] filter those failed segments when building record file --- MultiVidCam/AVAssetStitcher.m | 6 + MultiVidCam/RecordingViewController.h | 1 + MultiVidCam/RecordingViewController.m | 3 + MultiVidCam/VideoCameraInputManager.h | 2 + MultiVidCam/VideoCameraInputManager.m | 176 +++++++++++++----- .../en.lproj/MainStoryboard_iPad.storyboard | 108 ++++------- .../en.lproj/MainStoryboard_iPhone.storyboard | 96 +++++----- 7 files changed, 223 insertions(+), 169 deletions(-) diff --git a/MultiVidCam/AVAssetStitcher.m b/MultiVidCam/AVAssetStitcher.m index 0ae23cf..1c9da04 100644 --- a/MultiVidCam/AVAssetStitcher.m +++ b/MultiVidCam/AVAssetStitcher.m @@ -47,6 +47,12 @@ - (id)initWithOutputSize:(CGSize)outSize - (void)addAsset:(AVURLAsset *)asset withTransform:(CGAffineTransform (^)(AVAssetTrack *videoTrack))transformToApply withErrorHandler:(void (^)(NSError *error))errorHandler { + if ([[asset tracksWithMediaType:AVMediaTypeVideo] count] == 0 || + [[asset tracksWithMediaType:AVMediaTypeAudio] count] == 0) + { + return; + } + AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; diff --git a/MultiVidCam/RecordingViewController.h b/MultiVidCam/RecordingViewController.h index 69816f7..a51d4d0 100644 --- a/MultiVidCam/RecordingViewController.h +++ b/MultiVidCam/RecordingViewController.h @@ -36,5 +36,6 @@ - (IBAction)recordTouchUp:(id)sender; - (IBAction)saveRecording:(id)sender; - (IBAction)cancelAction:(id)sender; +- (IBAction)reverseAction:(id)sender; @end diff --git a/MultiVidCam/RecordingViewController.m b/MultiVidCam/RecordingViewController.m index 7054a5e..f8d89da 100644 --- a/MultiVidCam/RecordingViewController.m +++ b/MultiVidCam/RecordingViewController.m @@ -129,6 +129,9 @@ - (void)didReceiveMemoryWarning } #pragma mark Record button +- (IBAction)reverseAction:(id)sender { + [videoCameraInputManager reverseCamera]; +} - (IBAction)cancelAction:(id)sender { diff --git a/MultiVidCam/VideoCameraInputManager.h b/MultiVidCam/VideoCameraInputManager.h index 055a2a2..af0ef2b 100644 --- a/MultiVidCam/VideoCameraInputManager.h +++ b/MultiVidCam/VideoCameraInputManager.h @@ -35,6 +35,8 @@ typedef void (^ErrorHandlingBlock)(NSError *error); - (void)resumeRecording; - (void)reset; +- (void)reverseCamera; +- (void)cleanTemporaryFiles; - (void)finalizeRecordingToFile:(NSURL *)finalVideoLocationURL withVideoSize:(CGSize)videoSize withPreset:(NSString *)preset withCompletionHandler:(void (^)(NSError *error))completionHandler; diff --git a/MultiVidCam/VideoCameraInputManager.m b/MultiVidCam/VideoCameraInputManager.m index 3b9c684..01a6c54 100644 --- a/MultiVidCam/VideoCameraInputManager.m +++ b/MultiVidCam/VideoCameraInputManager.m @@ -22,7 +22,9 @@ #import -@interface VideoCameraInputManager (Private) +@interface VideoCameraInputManager () + +@property (nonatomic, strong) NSMutableDictionary *segmentFileDict; - (void)startNotificationObservers; - (void)endNotificationObservers; @@ -75,6 +77,7 @@ - (id)init inFlightWrites = 0; movieFileOutput = [[AVCaptureMovieFileOutput alloc] init]; + self.segmentFileDict = [[NSMutableDictionary alloc] init]; [self startNotificationObservers]; } @@ -83,6 +86,7 @@ - (id)init - (void)dealloc { + NSLog(@"Video Camera Input Manager dealloc"); [_captureSession removeOutput:movieFileOutput]; [self endNotificationObservers]; @@ -90,6 +94,7 @@ - (void)dealloc - (void)setupSessionWithPreset:(NSString *)preset withCaptureDevice:(AVCaptureDevicePosition)cd withTorchMode:(AVCaptureTorchMode)tm withError:(NSError **)error { + [self cleanTemporaryFiles]; if(setupComplete) { *error = [NSError errorWithDomain:@"Setup session already complete." code:102 userInfo:nil]; @@ -150,8 +155,6 @@ - (void)setupSessionWithPreset:(NSString *)preset withCaptureDevice:(AVCaptureDe - (void)startRecording { - [temporaryFileURLs removeAllObjects]; - uniqueTimestamp = [[NSDate date] timeIntervalSince1970]; currentRecordingSegment = 0; _isPaused = NO; @@ -163,13 +166,10 @@ - (void)startRecording videoConnection.videoOrientation = orientation; } - NSURL *outputFileURL = [NSURL fileURLWithPath:[self constructCurrentTemporaryFilename]]; - - [temporaryFileURLs addObject:outputFileURL]; - + NSString *filePath = [self constructCurrentTemporaryFilename]; + [self.segmentFileDict setObject:@(0) forKey:filePath]; movieFileOutput.maxRecordedDuration = (_maxDuration > 0) ? CMTimeMakeWithSeconds(_maxDuration, 600) : kCMTimeInvalid; - - [movieFileOutput startRecordingToOutputFileURL:outputFileURL recordingDelegate:self]; + [movieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:filePath] recordingDelegate:self]; } - (void)pauseRecording @@ -182,33 +182,50 @@ - (void)pauseRecording - (void)resumeRecording { - currentRecordingSegment++; _isPaused = NO; + currentRecordingSegment++; - NSURL *outputFileURL = [NSURL fileURLWithPath:[self constructCurrentTemporaryFilename]]; - - [temporaryFileURLs addObject:outputFileURL]; - - if(_maxDuration > 0) - { - movieFileOutput.maxRecordedDuration = CMTimeSubtract(CMTimeMakeWithSeconds(_maxDuration, 600), currentFinalDurration); - } - else - { - movieFileOutput.maxRecordedDuration = kCMTimeInvalid; - } - - [movieFileOutput startRecordingToOutputFileURL:outputFileURL recordingDelegate:self]; + NSString *filePath = [self constructCurrentTemporaryFilename]; + [self.segmentFileDict setObject:@(0) forKey:filePath]; + movieFileOutput.maxRecordedDuration = (_maxDuration > 0) ? CMTimeSubtract(CMTimeMakeWithSeconds(_maxDuration, 600), currentFinalDurration) : kCMTimeInvalid; + [movieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:filePath] recordingDelegate:self]; } - (void)reset { - if (movieFileOutput.isRecording) - { - [self pauseRecording]; - } - + [self pauseRecording]; _isPaused = NO; + [_captureSession stopRunning]; +} + +- (void)reverseCamera { + NSArray *inputs = self.captureSession.inputs; + for ( AVCaptureDeviceInput *input in inputs ) { + AVCaptureDevice *device = input.device; + if ([device hasMediaType:AVMediaTypeVideo]) { + AVCaptureDevicePosition position = device.position; + AVCaptureDevice *newCamera = nil; + AVCaptureDeviceInput *newInput = nil; + + if (position == AVCaptureDevicePositionFront) { + newCamera = [self cameraWithPosition:AVCaptureDevicePositionBack]; + } + else { + newCamera = [self cameraWithPosition:AVCaptureDevicePositionFront]; + } + newInput = [AVCaptureDeviceInput deviceInputWithDevice:newCamera error:nil]; + + // beginConfiguration ensures that pending changes are not applied immediately + [self.captureSession beginConfiguration]; + + [self.captureSession removeInput:input]; + [self.captureSession addInput:newInput]; + + // Changes take effect once the outermost commitConfiguration is invoked. + [self.captureSession commitConfiguration]; + break; + } + } } - (void)finalizeRecordingToFile:(NSURL *)finalVideoLocationURL withVideoSize:(CGSize)videoSize withPreset:(NSString *)preset withCompletionHandler:(void (^)(NSError *error))completionHandler @@ -218,13 +235,61 @@ - (void)finalizeRecordingToFile:(NSURL *)finalVideoLocationURL withVideoSize:(CG NSError *error; if([finalVideoLocationURL checkResourceIsReachableAndReturnError:&error]) { - completionHandler([NSError errorWithDomain:@"Output file already exists." code:104 userInfo:nil]); - return; + [[NSFileManager defaultManager] removeItemAtURL:finalVideoLocationURL error:nil]; } - if(inFlightWrites != 0) - { - completionHandler([NSError errorWithDomain:@"Can't finalize recording unless all sub-recorings are finished." code:106 userInfo:nil]); + //----------filter the failed files--------- + [temporaryFileURLs removeAllObjects]; + NSArray *keys = [self.segmentFileDict allKeys]; + NSArray *sortedArray = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + return [obj1 compare:obj2 options:NSNumericSearch]; + }]; + + for (NSString *filePath in sortedArray) { + NSInteger fileValue = [self.segmentFileDict[filePath] integerValue]; + if (fileValue == 0) { + NSLog(@"[0]not start and stop! filePath=%@", filePath); + if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; + } + } + else if (fileValue == 1) { + NSLog(@"[1]started but not stop! filePath=%@", filePath); + if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; + } + } + else if (fileValue == 2) { + NSLog(@"[2]stoped but not start! filePath=%@", filePath); + if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; + } + } + else if (fileValue == 3) { + NSLog(@"[3]OK start and stop success!!! filePath=%@", filePath); + if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + CGFloat fileSize = [self getFileSize:filePath];//KB + if (fileSize <= 10.0f) { + [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; + } + else { + [temporaryFileURLs addObject:[NSURL fileURLWithPath:filePath]]; + } + } + } + else { + NSLog(@"[%d]other error! filepath=%@", fileValue, filePath); + if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; + } + } + } + //------------------------------------------ + + + if ([temporaryFileURLs count] == 0) { + NSError *error1 = [NSError errorWithDomain:@"have no files!" code:108 userInfo:nil]; + completionHandler(error1); return; } @@ -279,15 +344,25 @@ - (void)finalizeRecordingToFile:(NSURL *)finalVideoLocationURL withVideoSize:(CG } else { - [self cleanTemporaryFiles]; - [temporaryFileURLs removeAllObjects]; - completionHandler(nil); } - + [self cleanTemporaryFiles]; }]; } +- (CGFloat)getFileSize:(NSString *)filePath{ + if ( ! [[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + return 0; + } + NSError *error=nil; + NSDictionary * fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error]; + if (fileAttributes == nil || error!=nil) { + return 0; + } + long long fileSizeByte = [fileAttributes fileSize]; + return fileSizeByte / 1024.0f; +} + - (CMTime)totalRecordingDuration { if(CMTimeCompare(kCMTimeZero, currentFinalDurration) == 0) @@ -305,11 +380,17 @@ - (CMTime)totalRecordingDuration - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections { - inFlightWrites++; + NSInteger value = [[self.segmentFileDict objectForKey:[fileURL path]] integerValue]; + value += 1; + self.segmentFileDict[[fileURL path]] = @(value); } - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections error:(NSError *)error { + NSInteger value = [[self.segmentFileDict objectForKey:[fileURL path]] integerValue]; + value += 2; + self.segmentFileDict[[fileURL path]] = @(value); + if(error) { if(self.asyncErrorHandler) @@ -321,8 +402,6 @@ - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToO NSLog(@"Error capturing output: %@", error); } } - - inFlightWrites--; } #pragma mark - Observer start and stop @@ -499,14 +578,21 @@ - (AVCaptureConnection *)connectionWithMediaType:(NSString *)mediaType fromConne - (NSString *)constructCurrentTemporaryFilename { - return [NSString stringWithFormat:@"%@%@-%ld-%d.mov", NSTemporaryDirectory(), @"recordingsegment", uniqueTimestamp, currentRecordingSegment]; + NSString *tempFolderPath = [NSString stringWithFormat:@"%@temprecordfiles",NSTemporaryDirectory()]; + if ( ! [[NSFileManager defaultManager] fileExistsAtPath:tempFolderPath]) { + [[NSFileManager defaultManager] createDirectoryAtPath:tempFolderPath withIntermediateDirectories:YES attributes:nil error:NULL]; + } + return [NSString stringWithFormat:@"%@/%@-%ld-%d.mov", tempFolderPath, @"recordingsegment", uniqueTimestamp, currentRecordingSegment]; } - (void)cleanTemporaryFiles { - [temporaryFileURLs enumerateObjectsUsingBlock:^(NSURL *temporaryFiles, NSUInteger idx, BOOL *stop) { - [[NSFileManager defaultManager] removeItemAtURL:temporaryFiles error:nil]; - }]; + NSString *tempFolderPath = [NSString stringWithFormat:@"%@temprecordfiles",NSTemporaryDirectory()]; + if ([[NSFileManager defaultManager] fileExistsAtPath:tempFolderPath]) { + [[NSFileManager defaultManager] removeItemAtPath:tempFolderPath error:nil]; + } + + [temporaryFileURLs removeAllObjects]; } @end diff --git a/MultiVidCam/en.lproj/MainStoryboard_iPad.storyboard b/MultiVidCam/en.lproj/MainStoryboard_iPad.storyboard index 06c9edd..f678d7e 100644 --- a/MultiVidCam/en.lproj/MainStoryboard_iPad.storyboard +++ b/MultiVidCam/en.lproj/MainStoryboard_iPad.storyboard @@ -1,26 +1,34 @@ - + - + + + + + + - + - + + - + + - - - - - - - - - - - - - - - - - - - - - - - + - - - - + + - + - - - - + - + - @@ -139,29 +118,8 @@ - - - - - - - - - - - - - - - - - - - - - - + diff --git a/MultiVidCam/en.lproj/MainStoryboard_iPhone.storyboard b/MultiVidCam/en.lproj/MainStoryboard_iPhone.storyboard index d6d2b00..60615b1 100644 --- a/MultiVidCam/en.lproj/MainStoryboard_iPhone.storyboard +++ b/MultiVidCam/en.lproj/MainStoryboard_iPhone.storyboard @@ -1,22 +1,41 @@ - + - + + + + + + - + - + + + + + - - - + - - - + + + - + - - + + - + - - + + - - + + @@ -138,27 +157,6 @@ - - - - - - - - - - - - - - - - - - - - -