AVFoundation实现小视频录制
2019-11-07 本文已影响0人
我只是个仙
模仿微信小视频功能
-
思路
利用系统AVCaptureSession来实现,添加video和audio的AVCaptureConnection
我们先来先看一下AVCaptureSession的官方文档 有一段important的description,一定要单独起一个线程
官方文档是个好东西
初始化AVCaptureSession
self.session = [AVCaptureSession new];
if ([self.session canSetSessionPreset:AVCaptureSessionPresetHigh]) {
[self.session setSessionPreset:AVCaptureSessionPresetHigh];
}
添加两个摄像头
//摄像头输入
- (AVCaptureDeviceInput *)backCameraInput {
if (_backCameraInput == nil) {
NSError *error;
_backCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self cameroWithPosition:AVCaptureDevicePositionBack] error:&error];
if (error) {
NSLog(@"后置摄像头获取失败");
}
}
return _backCameraInput;
}
- (AVCaptureDeviceInput *)frontCameraInput {
if (_frontCameraInput == nil) {
NSError *error;
_frontCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self cameroWithPosition:AVCaptureDevicePositionFront] error:&error];
if (error) {
NSLog(@"前置摄像头获取失败");
}
}
return _frontCameraInput;
}
- (void)setupInputs {
NSError *error = nil;
if ([self.session canAddInput:self.backCameraInput]) {
[self.session addInput:self.backCameraInput];
}
else {
[CCHUD showWhiteBackText:kGetLocalizedString(@"添加视频输入失败")];
return;
}
AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio] error:&error];
if (!audioInput) {
[CCHUD showWhiteBackText:kGetLocalizedString(@"初始化音频输入设备失败")];
return;
}
if ([self.session canAddInput:audioInput]) {
[self.session addInput:audioInput];
}
else {
[CCHUD showWhiteBackText:kGetLocalizedString(@"添加音频输入失败")];
return;
}
}
添加两个输出源
- (void)setupVideoOutput {
self.videoOutput = [[AVCaptureVideoDataOutput alloc] init];
[self.videoOutput setSampleBufferDelegate:self queue:self.videoQueue];
if ([self.session canAddOutput:self.videoOutput]) {
[self.session addOutput:self.videoOutput];
self.videoConnection = [self.videoOutput connectionWithMediaType:AVMediaTypeVideo];
[self.videoConnection setVideoOrientation:AVCaptureVideoOrientationPortrait];
self.videoConnection.videoMirrored = self.isFront;
}
else {
[CCHUD showWhiteBackText:kGetLocalizedString(@"添加视频输出失败")];
return;
}
self.audioOutput = [[AVCaptureAudioDataOutput alloc] init];
[self.audioOutput setSampleBufferDelegate:self queue:self.audioQueue];
if ([self.session canAddOutput:self.audioOutput]) {
[self.session addOutput:self.audioOutput];
self.audioConnection = [self.audioOutput connectionWithMediaType:AVMediaTypeAudio];
}
else {
[CCHUD showWhiteBackText:kGetLocalizedString(@"添加视频输出失败")];
return;
}
}
想要可见,还需要添加一个AVCaptureVideoPreviewLayer,layer负责绘制output的buffer逐帧绘制
- (void)setupPreviewLayer {
self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
self.previewLayer.frame = CGRectMake(0, WindCS_TOP_HEIGHT, CCScreenWidth, CCScreenHeight - WindCS_IPHONE_X_BOTTOM_HEIGHT - WindCS_TOP_HEIGHT);
self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.previewView.layer addSublayer:self.previewLayer];
self.previewView.layer.masksToBounds = YES;
}
官方文档是个好东西写出文件 这里还需要一个重要的东西 AVAssetWriter
开始录制
self.avPath = [[WindCSFilePathManager windCS_CoreVideoPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"%.4f.mp4",[[NSDate new] timeIntervalSince1970]]];
self.writer = [AVAssetWriter assetWriterWithURL:[NSURL fileURLWithPath:self.avPath] fileType:AVFileTypeMPEG4 error:nil];
self.writerVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:[self videoCompressSettings]];
self.writerVideoInput.expectsMediaDataInRealTime = YES;
if ([self.writer canAddInput:self.writerVideoInput]) {
[self.writer addInput:self.writerVideoInput];
}
self.writerAudioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:[self audioCompressSettings]];
self.writerAudioInput.expectsMediaDataInRealTime = YES;
if ([self.writer canAddInput:self.writerAudioInput]) {
[self.writer addInput:self.writerAudioInput];
}
WindCS_WEAK_SELF
[self adjustVideoOrientationComplete:^{
WindCS_STRONG_SELF
dispatch_async(dispatch_get_main_queue(), ^{
strongSelf.isRecording = YES;
});
}];
self.isRecording = YES;
output的配置
- (NSDictionary *)videoCompressSettings {
NSDictionary *compressionProperties = @{ AVVideoAverageBitRateKey : @(200 * 8 * 1024),
AVVideoExpectedSourceFrameRateKey: @25,
AVVideoProfileLevelKey : AVVideoProfileLevelH264HighAutoLevel };
CGFloat height = (CCScreenHeight - WindCS_TOP_HEIGHT - WindCS_IPHONE_X_BOTTOM_HEIGHT);
return @{ AVVideoCodecKey : AVVideoCodecH264,
AVVideoWidthKey : @(CCScreenWidth * 2),
AVVideoHeightKey : @(height * 2),
AVVideoCompressionPropertiesKey : compressionProperties,
AVVideoScalingModeKey : AVVideoScalingModeResizeAspectFill };
}
- (NSDictionary *)audioCompressSettings {
AudioChannelLayout layout = { .mChannelLayoutTag = kAudioChannelLayoutTag_Stereo,
.mChannelBitmap = 0,
.mNumberChannelDescriptions = 0 };
NSData *channelLayoutData = [NSData dataWithBytes:&layout length:offsetof(AudioChannelLayout, mChannelDescriptions)];
return @{ AVFormatIDKey: @(kAudioFormatMPEG4AAC),
AVEncoderBitRateKey : @96000,
AVSampleRateKey : @44100,
AVChannelLayoutKey : channelLayoutData,
AVNumberOfChannelsKey : @2 };
}
视频方向调整
/// 调整视频方向
- (void)adjustVideoOrientationComplete:(void(^)(void))finished {
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
if (orientation == UIDeviceOrientationLandscapeRight) {
self.writerVideoInput.transform = CGAffineTransformMakeRotation(M_PI / 2.0);
finished();
} else if (orientation == UIDeviceOrientationLandscapeLeft) {
self.writerVideoInput.transform = CGAffineTransformMakeRotation(M_PI * 1.5);
finished();
} else if (orientation == UIDeviceOrientationPortraitUpsideDown) {
self.writerVideoInput.transform = CGAffineTransformMakeRotation(M_PI);
finished();
} else if (orientation == UIDeviceOrientationUnknown || orientation == UIDeviceOrientationPortrait) {
if ([self.cmmotionManager isDeviceMotionAvailable]) {
self.cmmotionManager.deviceMotionUpdateInterval = 0;
WindCS_WEAK_SELF
[self.cmmotionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {
double x = motion.gravity.x;
double y = motion.gravity.y;
double xAngle = atan2(x,y)/M_PI*180.0;
WindCS_STRONG_SELF
if (xAngle < -45 && xAngle > -135) {
strongSelf.writerVideoInput.transform = CGAffineTransformMakeRotation(M_PI * 1.5);
} else if (xAngle > 45 && xAngle < 135) {
strongSelf.writerVideoInput.transform = CGAffineTransformMakeRotation(M_PI / 2.0);
} else if (xAngle >= -45 && xAngle <= 45) {
self.writerVideoInput.transform = CGAffineTransformMakeRotation(M_PI);
}
[strongSelf.cmmotionManager stopDeviceMotionUpdates];
finished();
}];
}
}
}
AVCaptureVideoDataOutputSampleBufferDelegate
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
// 录视频
if (self.isRecording) {
@synchronized (self) {
if (self.writer.status == AVAssetWriterStatusUnknown) {
[self.writer startWriting];
NSLog(@"self.writer startWriting");
[self.writer startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
}
[self write:sampleBuffer fromConnection:connection];
}
}
}
- (void)write:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
if (connection == self.videoConnection) {
if (self.writerVideoInput.readyForMoreMediaData) {
[self.writerVideoInput appendSampleBuffer:sampleBuffer];
}
}
else if (connection == self.audioConnection) {
if (self.writerAudioInput.readyForMoreMediaData) {
[self.writerAudioInput appendSampleBuffer:sampleBuffer];
}
}
}
停止录制
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.writer.inputs containsObject:self.writerVideoInput]) {
[self.writerVideoInput markAsFinished];
}
if ([self.writer.inputs containsObject:self.writerAudioInput]) {
[self.writerAudioInput markAsFinished];
}
[self.writer finishWritingWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"file size : %fM", [self showSize:self.avPath]);
};
}];
});
官方文档是个好东西
至此,一个小视频录制的功能其实就简单搭建完成了,当然 这里有很多细节,比如:
- 参数的配置 可以根据自己的需要去配置
- 写出来的视频如何压缩合适?
- 既然可以逐帧写入,name是否可以逐帧的编辑呢?
接下来有时间可以一起研究~
你的喜欢或者关注是我继续的动力哦~