1,add AVFoundation.framework,AssetsLibrary.framework。
第一个用于获取设备摄像头,话筒,第二个保存到相册将视频。
2,
//apple媒体框架 #import <AVFoundation/AVFoundation.h> //C内存管理库 #import <malloc/malloc.h> //apple 媒体文件框架 #import <AssetsLibrary/AssetsLibrary.h>
3,add delegate //AVCaptureVideoDataOutputSampleBufferDelegate视频输出代理 //AVCaptureAudioDataOutputSampleBufferDelegate音频输出代理 @interface FourController : UIViewController<AVCaptureVideoDataOutputSampleBufferDelegate,AVCaptureAudioDataOutputSampleBufferDelegate> 4,.h文件
// // FourController.h // VideoDemo // // Created by skyzizhu on 15/12/17. // Copyright (c) 2015年 skyzizhu. All rights reserved. // #import <UIKit/UIKit.h> #import <AVFoundation/AVFoundation.h> #import <malloc/malloc.h> #import <AssetsLibrary/AssetsLibrary.h> @interface FourController : UIViewController<AVCaptureVideoDataOutputSampleBufferDelegate,AVCaptureAudioDataOutputSampleBufferDelegate> //视频输出 @property (nonatomic,strong)AVCaptureVideoDataOutput *videoOutput; //音频输出 @property (nonatomic,strong)AVCaptureAudioDataOutput *audioOutput; //当前录制session @property (nonatomic,strong)AVCaptureSession *mediaSession; //视频写入文件 @property (nonatomic,strong)AVAssetWriterInput *videoWriterInput; //音频写入文件 @property (nonatomic,strong)AVAssetWriterInput *audioWriterInput; //流写入 @property (nonatomic,strong)AVAssetWriter *assetWriter; //单独获取视频可以用这个属性,备用 @property (nonatomic,strong)AVAssetWriterInputPixelBufferAdaptor *videoAssetWriterPixelBufferAdaptor; //layer //apple提供流媒体layer,用于时时展现录制的media @property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer; // @end
5,.m文件,分别获取设备的摄像头,话筒(输入设备)。
-(AVCaptureDeviceInput *)getVideoInput { NSError *error = nil; AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; return input; } -(AVCaptureDeviceInput *)getAudioInput { NSError *error = nil; AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; return input; }
分别配置视频的输出形式,配置类型等属性,这个是输出的属性,和写入的属性不同,视频和音频同事传入一个线程,两个用同一个线程就行,否则会崩溃,因为不同步。
-(AVCaptureVideoDataOutput *)getVideoOutputWithQueue:(dispatch_queue_t)queue { if(_videoOutput != nil){ return _videoOutput; } _videoOutput = [[AVCaptureVideoDataOutput alloc] init]; _videoOutput.alwaysDiscardsLateVideoFrames = YES; _videoOutput.videoSettings = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]; [_videoOutput setSampleBufferDelegate:self queue:queue]; return _videoOutput; } -(AVCaptureAudioDataOutput *)getAudioOutputWithQueue:(dispatch_queue_t)queue { if(_audioOutput != nil){ return _audioOutput; } _audioOutput = [[AVCaptureAudioDataOutput alloc] init]; [_audioOutput setSampleBufferDelegate:self queue:queue]; return _audioOutput; }
配置当前录制session,可以看成一种会话,分别将上边设置的输入输出添加到会话里边:
-(AVCaptureSession *)setMediaSession { if(_mediaSession != nil){ return _mediaSession; } _mediaSession = [[AVCaptureSession alloc] init]; //通知系统开始配置session [_mediaSession beginConfiguration]; _mediaSession.sessionPreset = AVCaptureSessionPresetLow; //提交当前配置 [_mediaSession commitConfiguration]; //add input and output [_mediaSession addInput:[self getVideoInput]]; [_mediaSession addInput:[self getAudioInput]]; //dispatch_queue_t videoQueue = dispatch_queue_create("com.videos.queue", NULL); //传入一个线程 dispatch_queue_t audioQueue = dispatch_queue_create("com.audios.queue", NULL); [_mediaSession addOutput:[self getVideoOutputWithQueue:audioQueue]]; [_mediaSession addOutput:[self getAudioOutputWithQueue:audioQueue]]; //开始录制 [_mediaSession startRunning]; return _mediaSession; }
画一个view,限制当前录制流的layer,顺便添加一个停止按钮,用于停止之后将文件保存在相册中:
-(void)setLayer { UIView *v = [[UIView alloc]initWithFrame:CGRectMake(50, 100, 280, 400)]; [self.view addSubview:v]; //当前流layer _captureVideoPreviewLayer=[[AVCaptureVideoPreviewLayer alloc]initWithSession:self.mediaSession]; CALayer *layer = v.layer; layer.masksToBounds=YES; _captureVideoPreviewLayer.frame=layer.bounds; //显示模式 _captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill; [layer addSublayer:_captureVideoPreviewLayer]; // UIButton *stop = [[UIButton alloc]initWithFrame:CGRectMake(200, 550, 100, 60)]; stop.center = CGPointMake(self.view.center.x, stop.center.y); stop.backgroundColor = [UIColor grayColor]; [stop setTitle:@"停止" forState:UIControlStateNormal]; [stop setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; [self.view addSubview:stop]; [stop addTarget:self action:@selector(stopAction:) forControlEvents:UIControlEventTouchUpInside]; }
配置AVAssetWriter,将视频流实时写入到文件中:
//save file path by document - (NSString *)generateFilePathForMovie { return [NSString stringWithFormat:@"%@/play.mp4", [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]]; } -(AVAssetWriter *)setMediaWriter { //首先判断当前文件的路径是否存在,如果存在则删除文件,否则写入会报错 if ([[NSFileManager defaultManager] fileExistsAtPath:[self generateFilePathForMovie]]) { NSLog(@"already exists"); NSError *error; if ([[NSFileManager defaultManager] removeItemAtPath:[self generateFilePathForMovie] error:&error] == NO) { NSLog(@"removeitematpath %@ error :%@", [self generateFilePathForMovie], error); } } // NSError *error = nil; //创建AVAssetWriter,用于添加写入流的形式 //写入器传一个文件的URL,本地文件的URL,然后会自动写入。 _assetWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:[self generateFilePathForMovie]] fileType:AVFileTypeQuickTimeMovie error:&error]; //[_assetWriter startSessionAtSourceTime:kCMTimeZero]; //video,配置视频的写入形式。 int bitRate = (300 + /*self.currentQuality*/5 * 90) * 1024; //NORMAL 750 * 1024 NSDictionary *codecSettings = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:bitRate], AVVideoAverageBitRateKey, nil]; //h264 NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys: AVVideoCodecH264, AVVideoCodecKey, [NSNumber numberWithInt:480], AVVideoWidthKey, [NSNumber numberWithInt:320], AVVideoHeightKey, codecSettings, AVVideoCompressionPropertiesKey, nil]; _videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings]; _videoWriterInput.expectsMediaDataInRealTime = YES; /*这个注释的是单独写入视频用这个。 self.videoAssetWriterPixelBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc] initWithAssetWriterInput:_videoWriterInput sourcePixelBufferAttributes: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA],kCVPixelBufferPixelFormatTypeKey, nil]]; */ //audio // Add the audio input //配置音频,aac AudioChannelLayout acl; bzero( &acl, sizeof(acl)); acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; NSDictionary* audioOutputSettings = nil; // Both type of audio inputs causes output video file to be corrupted. if( /* DISABLES CODE */ (NO) ) { // should work from iphone 3GS on and from ipod 3rd generation audioOutputSettings = [NSDictionary dictionaryWithObjectsAndKeys: [ NSNumber numberWithInt: kAudioFormatMPEG4AAC ], AVFormatIDKey, [ NSNumber numberWithInt: 1 ], AVNumberOfChannelsKey, [ NSNumber numberWithFloat: 44100.0 ], AVSampleRateKey, [ NSNumber numberWithInt: 64000 ], AVEncoderBitRateKey, [ NSData dataWithBytes: &acl length: sizeof( acl ) ], AVChannelLayoutKey, nil]; } else { // should work on any device requires more space audioOutputSettings = [ NSDictionary dictionaryWithObjectsAndKeys: [ NSNumber numberWithInt: kAudioFormatAppleLossless ], AVFormatIDKey, [ NSNumber numberWithInt: 16 ], AVEncoderBitDepthHintKey, [ NSNumber numberWithFloat: 44100.0 ], AVSampleRateKey, [ NSNumber numberWithInt: 1 ], AVNumberOfChannelsKey, [ NSData dataWithBytes: &acl length: sizeof( acl ) ], AVChannelLayoutKey, nil ]; } _audioWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType: AVMediaTypeAudio outputSettings: audioOutputSettings ]; //视频和音频的expectsMediaDataInRealTime属性必须是yes,这样才能获取实时数据 _audioWriterInput.expectsMediaDataInRealTime = YES; //将视频写入和音频写入加入到媒体写入器里边 [_assetWriter addInput:_videoWriterInput]; [_assetWriter addInput:_audioWriterInput]; return _assetWriter; }
获取实时的代理方法:
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { //sampleBuffer是实时流,转换成data,查看大小 NSData *data = [NSData dataWithBytes:&sampleBuffer length:malloc_size(sampleBuffer)]; NSLog(@"%ld",data.length); if(!CMSampleBufferDataIsReady(sampleBuffer)){ NSLog( @"sample buffer is not ready. Skipping sample" ); return; } //设置写入器的写入时间,开启写入 if (_assetWriter.status == AVAssetWriterStatusUnknown) { CMTime startTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); [_assetWriter startWriting]; [_assetWriter startSessionAtSourceTime:startTime]; } if(_assetWriter.status == AVAssetWriterStatusFailed){ NSLog(@"error - %@",_assetWriter.error); } //判断如果正在读取,则直接写入 if(_assetWriter.status == AVAssetWriterStatusWriting){ //写入视频 if([captureOutput isKindOfClass:[_audioOutput class]]){ [_audioWriterInput appendSampleBuffer:sampleBuffer]; } //写入音频 if([captureOutput isKindOfClass:[_videoOutput class]]){ [_videoWriterInput appendSampleBuffer:sampleBuffer]; } }
button event
#pragma mark - button action -(void)stopAction:(UIButton *)bt { //停止录制 [_mediaSession stopRunning]; //写入器写入完成调用的方法 [_assetWriter finishWritingWithCompletionHandler:^{ NSString *filePath = [self generateFilePathForMovie]; NSData *data = [NSData dataWithContentsOfFile:filePath]; NSLog(@"%@",filePath); NSLog(@"data = = = = %ld",data.length); //写入到相册 [self saveMedia:filePath]; }]; } #pragma mark - save media - (void)saveMedia:(NSString*)urlString{ ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; [library writeVideoAtPathToSavedPhotosAlbum:[NSURL fileURLWithPath:urlString] completionBlock:^(NSURL *assetURL, NSError *error) { NSLog(@"%@",assetURL); if (error && assetURL == nil) { NSLog(@"Save video fail:%@",error); } else { NSLog(@"Save video succeed."); if ([[NSFileManager defaultManager] fileExistsAtPath:[self generateFilePathForMovie]]) { NSError *error; if ([[NSFileManager defaultManager] removeItemAtPath:[self generateFilePathForMovie] error:&error] == NO) { NSLog(@"removeitematpath %@ error :%@", [self generateFilePathForMovie], error); } } } }]; }
调用:
- (void)viewDidLoad { [super viewDidLoad]; //write //先配置写入,然后录制,否则两个不在同一个线程,导致崩溃 [self setMediaWriter]; //session [self setMediaSession]; //add media layer [self setLayer]; // Do any additional setup after loading the view. }
延伸:
如果单独写入视频,则用AVAssetWriterInputPixelBufferAdaptor类型就可以写入,在代理方法里边:
#pragma mark - avcapturevideo delegate - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { //write //转换成imageBuffer CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); // a very dense way to keep track of the time at which this frame // occurs relative to the output stream, but it's just an example! //CFRelease(sampleBuffer); //通过assetWriterPixelBufferAdaptor属性写入视频 static int64_t frameNumber = 0; if(self.assetWriterInput.readyForMoreMediaData) [self.assetWriterPixelBufferAdaptor appendPixelBuffer:imageBuffer withPresentationTime:CMTimeMake(frameNumber, 14)]; frameNumber++; /*/ // /* NSData *data = [NSData dataWithBytes:&sampleBuffer length:malloc_size(sampleBuffer)]; NSLog(@"temp - - %ld",data.length); [self recieveVideoFromData:data]; */ }
如果不想用系统的实时layer,则可以将转换的data转换成iamge,通过切换iamgeview的图片实时播放视频:
//将buffer转换的data转换成image,放在view上 -(void)recieveVideoFromData:(NSData *)data{ CMSampleBufferRef sampleBuffer; [data getBytes:&sampleBuffer length:sizeof(sampleBuffer)]; CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CVPixelBufferLockBaseAddress(imageBuffer,0); uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(imageBuffer); size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); size_t width = CVPixelBufferGetWidth(imageBuffer); size_t height = CVPixelBufferGetHeight(imageBuffer); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef newContext = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); CGImageRef newImage = CGBitmapContextCreateImage(newContext); CGContextRelease(newContext); CGColorSpaceRelease(colorSpace); UIImage *image= [UIImage imageWithCGImage:newImage scale:1.0 orientation:UIImageOrientationRight]; CGImageRelease(newImage); [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES]; CVPixelBufferUnlockBaseAddress(imageBuffer,0); }