Still and Video Media Capture by using AVFoundation
This post hasn't been updated for 3 years
Trong quá trình làm việc có thể bạn sẽ gặp phải yêu cầu tạo ra 1 sản phẩm trong đó có chức năng tạo ra 1 đoạn video. Với trường hợp này bạn có thể nghĩ ngay tới việc sử dụng UIImagePickerController để có thể sử dụng luôn camera default, nhưng nếu UI đòi hỏi bạn phải customer khá nhiều, ví dụ như thay đổi kích thước khung hình, thêm icon, thêm button ... thì sẽ gặp khá nhiều khó khăn. Trong trường hợp như vậy có 1 giải pháp khác là sử dụng AVFoundation. Bạn có thể customer thoải mái UI 1 cách thuận tiện thay vì phải sử dụng camera default của iOS.
Về cơ bản, AVFoundation là mộ trong những framework cho phép chúng ta sử dụng để pay hay tạo video hay image. Dưới đây là kiến trúc tổng thể của AVFoundation :
Dựa vào mô hình này, chúng ta thấy nếu dùng lớp trừu tượng ở mức cao: AVKit, UIKit sẽ có rất nhiều thuận tiện, ví dụ như:
- Nếu bạn đơn giản muốn play movies, thì chỉ cần sử dụng AVKit framework.
- Trong iOS, để ghi video bạn có thể dung UIKit framework (UIImagePickerController)
Tuy nhiên bài viết này hướng tới sử dụng các lớp trừu tượng thấp hơn của AVFoundation.
Nhìn vào mô hình trên giúp chúng ta hiểu được phương thức AVFoundation hoạt động ra sao.
- Việc ghi nhận hình từ sensor: front-back camera, video, được quản lý bởi AVCapture Device Input.
- Việc tạo các tệp media được quản lý bởi AVCaptureMovieFileOutput, AVCaptureStillImageOut, AVCaptureVideoPreviewLayer.
- AVCaptureSession sẽ quản lý việc kết nối giữa sensor với giao diện tạo tệp media.
Detail hơn chúng ta có thể nhìn vào kiến trúc sau:
Về cơ bản chúng ta sẽ khởi tạo 1 session như sau:
// Create the session
_captureSession = [[AVCaptureSession alloc] init];
// Configure the session to pro1duce lower resolution video frames, if your
// processing algorithm can cope. We'll specify medium quality for the
// chosen device.
_captureSession.sessionPreset = AVCaptureSessionPresetPhoto;
Chúng ta có thể sử dụng cài đặt sẵn trong session để chỉ định chất lượng ảnh, độ phân giải mà bạn mong muốn.
Tiếp theo là việc kết nối Deviec input và phương thức tạo tệp media
// Find a suitable AVCaptureDevice
NSArray *cameras=[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
if (cameras.count == 0) {
return;
}
AVCaptureDevice *device = [cameras objectAtIndex:0];
// Create a device input with the device and add it to the session.
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input)
{
NSLog(@"PANIC: no media input");
}
[_captureSession addInput:input];
//Photo
_stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = @{ AVVideoCodecKey : AVVideoCodecJPEG};
[_stillImageOutput setOutputSettings:outputSettings];
[_captureSession addOutput:_stillImageOutput];
// Assign session to an ivar.
[self _configPreViewLayer:_captureSession];
[_captureSession startRunning];
}
- (void)_configPreViewLayer:(AVCaptureSession*)session {
NSLog(@"setting camera view");
_previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:session];
if (self.interfaceOrientation == UIDeviceOrientationLandscapeLeft) {
if (__IPHONE_5_0) {
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, -M_PI_2, 0, 0, 1);
_previewLayer.transform = transform;
}else {
[_previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight];
}
}
else if (self.interfaceOrientation == UIDeviceOrientationLandscapeRight) {
if (__IPHONE_5_0) {
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, M_PI_2, 0, 0, 1);
_previewLayer.transform = transform;
}else {
[_previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeLeft];
}
}
CGRect frame = self.v_capture.frame;
CGRect videoRect = CGRectMake(frame.origin.x, IS_WIDESCREEN? frame.origin.y:-10, IS_WIDESCREEN? frame.size.width:frame.size.width - 83,frame.size.height); // 83 is right panel's width
_previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
_previewLayer.frame = videoRect; // Assume you want the preview layer to fill the view.
[_previewLayer setBackgroundColor:[[UIColor clearColor] CGColor]];
[_previewLayer setBorderWidth:0];
[self.view.layer addSublayer:_previewLayer];
CALayer *__btnCapture = self.btn_capture.layer;
[self.view.layer addSublayer:__btnCapture];
}
Ở cuối funtion _setupSession , sau khi tạo thành công session, device input và phương thức tập tin media, chúng ta thực hiện start quá trình [_captureSession startRunning];
Để xử lý việc tạo ra 1 tập tin khá đơn giản, ví dụ như sau
- (IBAction)clicked_capture:(id)sender {
#if TARGET_IPHONE_SIMULATOR
[ self image:[ UIImage imageWithContentsOfFile:[[ NSBundle mainBundle ] pathForResource:@"test" ofType:@"jpg" ]] didFinishSavingWithError:nil contextInfo:NULL ];
#else
self.btn_capture.enabled = NO;
AVCaptureConnection *videoConnection = nil;
for (AVCaptureConnection *connection in _stillImageOutput.connections) {
for (AVCaptureInputPort *port in [connection inputPorts]) {
if ([[port mediaType] isEqual:AVMediaTypeVideo]) {
videoConnection = connection;
break;
}
}
if (videoConnection) {
break;
}
}
NSLog(@"about to request a capture from: %@", _stillImageOutput);
[_stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection
completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
CFDictionaryRef exifAttachments = CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
if (exifAttachments) {
NSLog(@"attachements: %@", exifAttachments);
} else {
NSLog(@"no attachments");
}
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
UIImage *image = [[UIImage alloc] initWithData:imageData];
_currentImage = image;
[_captureSession stopRunning];
UIImageWriteToSavedPhotosAlbum(_currentImage, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
// [[NSNotificationCenter defaultCenter] postNotificationName:kImageCapturedSuccessfully object:nil];
}];
#endif
}
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
if (error == nil) {
PIPilgrimageModeData *pilgrimage = [ PIPilgrimageModeData shareInstance ];
pilgrimage.fromCameraMode = kPilgrimageCaptured;
pilgrimage.photo = image;
NSLog( @"IMAGE SIZE: %i", [ UIImageJPEGRepresentation( image, 0.0f ) length ]);
[self dismissViewControllerAnimated:YES completion:nil];
}
else
[_captureSession startRunning];
self.btn_capture.enabled = YES;
}
Như vậy là chúng ta đã hoàn tất việc tạo ra 1 file ảnh. Hy vọng nó có ích cho các bạn.
All Rights Reserved