iOS开发进阶 - 用AVFoundation自定义视频录制功能

iOS扫码一图多码原生处理AVCaptureSession 业务中需要扫多个码(详细一点是一图多码),有点东西的。 阅读详情

如果移动端访问不佳,请访问我的个人博客

系统自带的录制视频的功能显然无法满足美工和项目经理的要求,自定义视频录制就非常重要了,那么下面来带大家制作属于自己的视频录制界面。

效果图

简介

自定义视频录制需要用到的框架主要是AVFoundation和CoreMedia,包括视频输出,输入和文件的读写,下面给大家罗列一下将要用到的类:

  • AVCaptureSession
  • AVCaptureVideoPreviewLayer
  • AVCaptureDeviceInput
  • AVCaptureConnection
  • AVCaptureVideoDataOutput
  • AVCaptureAudioDataOutput
  • AVAssetWriter
  • AVAssetWriterInput

下面详细介绍每个类和代码实现

AVCaptureSession

AVCaptureSession是AVFoundation捕捉类的中心枢纽,我们先从这个类入手,在视频捕获时,客户端可以实例化AVCaptureSession并添加适当的AVCaptureInputs、AVCaptureDeviceInput和输出,比如AVCaptureMovieFileOutput。通过[AVCaptureSession startRunning]开始数据流从输入到输出,和[AVCaptureSession stopRunning]停止输出输入的流动。客户端可以通过设置sessionPreset属性定制录制质量水平或输出的比特率。

//捕获视频的会话
- (AVCaptureSession *)recordSession {
    if (_recordSession == nil) {
        _recordSession = [[AVCaptureSession alloc] init];
        //添加后置摄像头的输出
        if ([_recordSession canAddInput:self.backCameraInput]) {
            [_recordSession addInput:self.backCameraInput];
        }
        //添加后置麦克风的输出
        if ([_recordSession canAddInput:self.audioMicInput]) {
            [_recordSession addInput:self.audioMicInput];
        }
        //添加视频输出
        if ([_recordSession canAddOutput:self.videoOutput]) {
            [_recordSession addOutput:self.videoOutput];
            //设置视频的分辨率为后置摄像头
            NSDictionary* actual = self.videoOutput.videoSettings;
            _cx = [[actual objectForKey:@"Height"] integerValue];
            _cy = [[actual objectForKey:@"Width"] integerValue];
        }
        //添加音频输出
        if ([_recordSession canAddOutput:self.audioOutput]) {
            [_recordSession addOutput:self.audioOutput];
        }
        //设置视频录制的方向
        self.videoConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
    }
    return _recordSession;
}

AVCaptureDevice

AVCaptureDevice的每个实例对应一个设备,如摄像头或麦克风。AVCaptureDevice的实例不能直接创建。所有现有设备可以使用类方法devicesWithMediaType:defaultDeviceWithMediaType:获取,设备可以提供一个或多个给定流媒体类型。AVCaptureDevice实例可用于提供给AVCaptureSession创建一个为AVCaptureDeviceInput类型的输入源。

//返回前置摄像头
- (AVCaptureDevice *)frontCamera {
    return [self cameraWithPosition:AVCaptureDevicePositionFront];
}

//返回后置摄像头
- (AVCaptureDevice *)backCamera {
    return [self cameraWithPosition:AVCaptureDevicePositionBack];
}

//用来返回是前置摄像头还是后置摄像头
- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition) position {
    //返回和视频录制相关的所有默认设备
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    //遍历这些设备返回跟position相关的设备
    for (AVCaptureDevice *device in devices) {
        if ([device position] == position) {
            return device;
        }
    }
    return nil;
}
//开启闪光灯
- (void)openFlashLight {
    AVCaptureDevice *backCamera = [self backCamera];
    if (backCamera.torchMode == AVCaptureTorchModeOff) {
        [backCamera lockForConfiguration:nil];
        backCamera.torchMode = AVCaptureTorchModeOn;
        backCamera.flashMode = AVCaptureFlashModeOn;
        [backCamera unlockForConfiguration];
    }
}
//关闭闪光灯
- (void)closeFlashLight {
    AVCaptureDevice *backCamera = [self backCamera];
    if (backCamera.torchMode == AVCaptureTorchModeOn) {
        [backCamera lockForConfiguration:nil];
        backCamera.torchMode = AVCaptureTorchModeOff;
        backCamera.flashMode = AVCaptureTorchModeOff;
        [backCamera unlockForConfiguration];
    }
}

AVCaptureDeviceInput

AVCaptureDeviceInput 是AVCaptureSession输入源,提供媒体数据从设备连接到系统,通过AVCaptureDevice的实例化得到,就是我们将要用到的设备输出源设备,也就是前后摄像头,通过[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]方法获得。

//后置摄像头输入
- (AVCaptureDeviceInput *)backCameraInput {
    if (_backCameraInput == nil) {
        NSError *error;
        _backCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self backCamera] error:&error];
        if (error) {
            [SVProgressHUD showErrorWithStatus:@"获取后置摄像头失败~"];
        }
    }
    return _backCameraInput;
}

//前置摄像头输入
- (AVCaptureDeviceInput *)frontCameraInput {
    if (_frontCameraInput == nil) {
        NSError *error;
        _frontCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self frontCamera] error:&error];
        if (error) {
            [SVProgressHUD showErrorWithStatus:@"获取前置摄像头失败~"];
        }
    }
    return _frontCameraInput;
}

AVCaptureVideoPreviewLayer

是CoreAnimation里面layer的一个子类,用来做为AVCaptureSession预览视频输出,简单来说就是来做为拍摄的视频呈现的一个layer。

//捕获到的视频呈现的layer
- (AVCaptureVideoPreviewLayer *)previewLayer {
    if (_previewLayer == nil) {
        //通过AVCaptureSession初始化
        AVCaptureVideoPreviewLayer *preview = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.recordSession];
        //设置比例为铺满全屏
        preview.videoGravity = AVLayerVideoGravityResizeAspectFill;
        _previewLayer = preview;
    }
    return _previewLayer;
}

AVCaptureMovieFileOutput

AVCaptureMovieFileOutput是AVCaptureFileOutput的子类,用来写入QuickTime视频类型的媒体文件。因为这个类在iphone上并不能实现暂停录制,和不能定义视频文件的类型,所以在这里并不使用,而是用灵活性更强的AVCaptureVideoDataOutput和AVCaptureAudioDataOutput来实现视频的录制。

AVCaptureVideoDataOutput

AVCaptureVideoDataOutput是AVCaptureOutput一个子类,可以用于用来输出未压缩或压缩的视频捕获的帧,AVCaptureVideoDataOutput产生的实例可以使用其他媒体视频帧适合的api处理,应用程序可以用captureOutput:didOutputSampleBuffer:fromConnection:代理方法来获取帧数据。

//视频输出
- (AVCaptureVideoDataOutput *)videoOutput {
    if (_videoOutput == nil) {
        _videoOutput = [[AVCaptureVideoDataOutput alloc] init];
        [_videoOutput setSampleBufferDelegate:self queue:self.captureQueue];
        NSDictionary* setcapSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange], kCVPixelBufferPixelFormatTypeKey,
                                        nil];
        _videoOutput.videoSettings = setcapSettings;
    }
    return _videoOutput;
}

AVCaptureAudioDataOutput

AVCaptureAudioDataOutput是AVCaptureOutput的子类,可用于用来输出捕获来的非压缩或压缩的音频样本,AVCaptureAudioDataOutput产生的实例可以使用其他媒体视频帧适合的api处理,应用程序可以用captureOutput:didOutputSampleBuffer:fromConnection:代理方法来获取音频数据。

//音频输出
- (AVCaptureAudioDataOutput *)audioOutput {
    if (_audioOutput == nil) {
        _audioOutput = [[AVCaptureAudioDataOutput alloc] init];
        [_audioOutput setSampleBufferDelegate:self queue:self.captureQueue];
    }
    return _audioOutput;
}

AVCaptureConnection

AVCaptureConnection代表AVCaptureInputPort或端口之间的连接,和一个AVCaptureOutput或AVCaptureVideoPreviewLayer在AVCaptureSession中的呈现。

//视频连接
- (AVCaptureConnection *)videoConnection {
    _videoConnection = [self.videoOutput connectionWithMediaType:AVMediaTypeVideo];
    return _videoConnection;
}

//音频连接
- (AVCaptureConnection *)audioConnection {
    if (_audioConnection == nil) {
        _audioConnection = [self.audioOutput connectionWithMediaType:AVMediaTypeAudio];
    }
    return _audioConnection;
}

AVAssetWriter

AVAssetWriter为写入媒体数据到一个新的文件提供服务,AVAssetWriter的实例可以规定写入媒体文件的格式,如QuickTime电影文件格式或MPEG-4文件格式等等。AVAssetWriter有多个并行的轨道媒体数据,基本的有视频轨道和音频轨道,将会在下面介绍。AVAssetWriter的单个实例可用于一次写入一个单一的文件。那些希望写入多次文件的客户端必须每一次用一个新的AVAssetWriter实例。

//初始化方法
- (instancetype)initPath:(NSString*)path Height:(NSInteger)cy width:(NSInteger)cx channels:(int)ch samples:(Float64) rate {
    self = [super init];
    if (self) {
        self.path = path;
        //先把路径下的文件给删除掉,保证录制的文件是最新的
        [[NSFileManager defaultManager] removeItemAtPath:self.path error:nil];
        NSURL* url = [NSURL fileURLWithPath:self.path];
        //初始化写入媒体类型为MP4类型
        _writer = [AVAssetWriter assetWriterWithURL:url fileType:AVFileTypeMPEG4 error:nil];
        //使其更适合在网络上播放
        _writer.shouldOptimizeForNetworkUse = YES;
        //初始化视频输出
        [self initVideoInputHeight:cy width:cx];
        //确保采集到rate和ch
        if (rate != 0 && ch != 0) {
            //初始化音频输出
            [self initAudioInputChannels:ch samples:rate];
        }
    }
    return self;
}

AVAssetWriterInput

用AVAssetWriterInput去拼接一个多媒体样本类型为CMSampleBuffer的实例到AVAssetWriter对象的输出文件的一个轨道;当有多个输入时, AVAssetWriter试图在用于存储和播放效率的理想模式写媒体数据。它的每一个输入信号,是否能接受媒体的数据根据通过readyForMoreMediaData的值来判断。如果readyForMoreMediaData是YES ,说明输入可以接受媒体数据。并且你只能媒体数据追加到输入端。

//初始化视频输入
- (void)initVideoInputHeight:(NSInteger)cy width:(NSInteger)cx {
    //录制视频的一些配置,分辨率,编码方式等等
    NSDictionary* settings = [NSDictionary dictionaryWithObjectsAndKeys:
                              AVVideoCodecH264, AVVideoCodecKey,
                              [NSNumber numberWithInteger: cx], AVVideoWidthKey,
                              [NSNumber numberWithInteger: cy], AVVideoHeightKey,
                              nil];
    //初始化视频写入类
    _videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:settings];
    //表明输入是否应该调整其处理为实时数据源的数据
    _videoInput.expectsMediaDataInRealTime = YES;
    //将视频输入源加入
    [_writer addInput:_videoInput];
}

//初始化音频输入
- (void)initAudioInputChannels:(int)ch samples:(Float64)rate {
    //音频的一些配置包括音频各种这里为AAC,音频通道、采样率和音频的比特率
    NSDictionary *settings = [NSDictionary dictionaryWithObjectsAndKeys:
                              [ NSNumber numberWithInt: kAudioFormatMPEG4AAC], AVFormatIDKey,
                              [ NSNumber numberWithInt: ch], AVNumberOfChannelsKey,
                              [ NSNumber numberWithFloat: rate], AVSampleRateKey,
                              [ NSNumber numberWithInt: 128000], AVEncoderBitRateKey,
                              nil];
    //初始化音频写入类
    _audioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:settings];
    //表明输入是否应该调整其处理为实时数据源的数据
    _audioInput.expectsMediaDataInRealTime = YES;
    //将音频输入源加入
    [_writer addInput:_audioInput];

}

上面是录制之前的一些需要的类和配置,下面介绍的是如何将获取到的数据呈现出来和怎样进行文件写入

写入数据

#pragma mark - 写入数据
- (void) captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    BOOL isVideo = YES;
    @synchronized(self) {
        if (!self.isCapturing  || self.isPaused) {
            return;
        }
        if (captureOutput != self.videoOutput) {
            isVideo = NO;
        }

        //初始化编码器,当有音频和视频参数时创建编码器
        if ((self.recordEncoder == nil) && !isVideo)                     
        {  
            CMFormatDescriptionRef fmt = CMSampleBufferGetFormatDescription(sampleBuffer);
            [self setAudioFormat:fmt];
            NSString *videoName = [NSString getUploadFile_type:@"video" fileType:@"mp4"];
            self.videoPath = [[self getVideoCachePath] stringByAppendingPathComponent:videoName];
            self.recordEncoder = [WCLRecordEncoder encoderForPath:self.videoPath Height:_cy width:_cx channels:_channels samples:_samplerate];
        }

        //判断是否中断录制过
        if (self.discont) {
            if (isVideo) {
                return;
            }
            self.discont = NO;
            // 计算暂停的时间
            CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
            CMTime last = isVideo ? _lastVideo : _lastAudio;
            if (last.flags & kCMTimeFlags_Valid) {
                if (_timeOffset.flags & kCMTimeFlags_Valid) {
                    pts = CMTimeSubtract(pts, _timeOffset);
                }
                CMTime offset = CMTimeSubtract(pts, last);
                if (_timeOffset.value == 0) {
                    _timeOffset = offset;
                }else {
                    _timeOffset = CMTimeAdd(_timeOffset, offset);
                }
            }
            _lastVideo.flags = 0;
            _lastAudio.flags = 0;
        }
        // 增加sampleBuffer的引用计时,这样我们可以释放这个或修改这个数据,防止在修改时被释放
        CFRetain(sampleBuffer);
        if (_timeOffset.value > 0) {
            CFRelease(sampleBuffer);
            //根据得到的timeOffset调整
            sampleBuffer = [self adjustTime:sampleBuffer by:_timeOffset];
        }
        // 记录暂停上一次录制的时间
        CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
        CMTime dur = CMSampleBufferGetDuration(sampleBuffer);
        if (dur.value > 0) {
            pts = CMTimeAdd(pts, dur);
        }
        if (isVideo) {
            _lastVideo = pts;
        }else {
            _lastAudio = pts;
        }
    }
    CMTime dur = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
    if (self.startTime.value == 0) {
        self.startTime = dur;
    }
    CMTime sub = CMTimeSubtract(dur, self.startTime);
    self.currentRecordTime = CMTimeGetSeconds(sub);
    if (self.currentRecordTime > self.maxRecordTime) {
        if (self.currentRecordTime - self.maxRecordTime < 0.1) {
            if ([self.delegate respondsToSelector:@selector(recordProgress:)]) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self.delegate recordProgress:self.currentRecordTime/self.maxRecordTime];
                });
            }
        }
        return;
    }
    if ([self.delegate respondsToSelector:@selector(recordProgress:)]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.delegate recordProgress:self.currentRecordTime/self.maxRecordTime];
        });
    }
    // 进行数据编码
    [self.recordEncoder encodeFrame:sampleBuffer isVideo:isVideo];
    CFRelease(sampleBuffer);
}

//设置音频格式
- (void)setAudioFormat:(CMFormatDescriptionRef)fmt {
    const AudioStreamBasicDescription *asbd = CMAudioFormatDescriptionGetStreamBasicDescription(fmt);
    _samplerate = asbd->mSampleRate;
    _channels = asbd->mChannelsPerFrame;

}

//调整媒体数据的时间
- (CMSampleBufferRef)adjustTime:(CMSampleBufferRef)sample by:(CMTime)offset {
    CMItemCount count;
    CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count);
    CMSampleTimingInfo* pInfo = malloc(sizeof(CMSampleTimingInfo) * count);
    CMSampleBufferGetSampleTimingInfoArray(sample, count, pInfo, &count);
    for (CMItemCount i = 0; i < count; i++) {
        pInfo[i].decodeTimeStamp = CMTimeSubtract(pInfo[i].decodeTimeStamp, offset);
        pInfo[i].presentationTimeStamp = CMTimeSubtract(pInfo[i].presentationTimeStamp, offset);
    }
    CMSampleBufferRef sout;
    CMSampleBufferCreateCopyWithNewTiming(nil, sample, count, pInfo, &sout);
    free(pInfo);
    return sout;
}

//通过这个方法写入数据
- (BOOL)encodeFrame:(CMSampleBufferRef) sampleBuffer isVideo:(BOOL)isVideo {
    //数据是否准备写入
    if (CMSampleBufferDataIsReady(sampleBuffer)) {
        //写入状态为未知,保证视频先写入
        if (_writer.status == AVAssetWriterStatusUnknown && isVideo) {
            //获取开始写入的CMTime
            CMTime startTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
            //开始写入
            [_writer startWriting];
            [_writer startSessionAtSourceTime:startTime];
        }
        //写入失败
        if (_writer.status == AVAssetWriterStatusFailed) {
            NSLog(@"writer error %@", _writer.error.localizedDescription);
            return NO;
        }
        //判断是否是视频
        if (isVideo) {
            //视频输入是否准备接受更多的媒体数据
            if (_videoInput.readyForMoreMediaData == YES) {
                //拼接数据
                [_videoInput appendSampleBuffer:sampleBuffer];
                return YES;
            }
        }else {
            //音频输入是否准备接受更多的媒体数据
            if (_audioInput.readyForMoreMediaData) {
                //拼接数据
                [_audioInput appendSampleBuffer:sampleBuffer];
                return YES;
            }
        }
    }
    return NO;
}

完成录制并写入相册

//停止录制
- (void) stopCaptureHandler:(void (^)(UIImage *movieImage))handler {
    @synchronized(self) {
        if (self.isCapturing) {
            NSString* path = self.recordEncoder.path;
            NSURL* url = [NSURL fileURLWithPath:path];
            self.isCapturing = NO;
            dispatch_async(_captureQueue, ^{
                [self.recordEncoder finishWithCompletionHandler:^{
                    self.isCapturing = NO;
                    self.recordEncoder = nil;
                    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
                        [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url];
                    } completionHandler:^(BOOL success, NSError * _Nullable error) {
                        NSLog(@"保存成功");
                    }];
                    [self movieToImageHandler:handler];
                }];
            });
        }
    }
}

//获取视频第一帧的图片
- (void)movieToImageHandler:(void (^)(UIImage *movieImage))handler {
    NSURL *url = [NSURL fileURLWithPath:self.videoPath];
    AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
    AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
    generator.appliesPreferredTrackTransform = TRUE;
    CMTime thumbTime = CMTimeMakeWithSeconds(0, 60);
    generator.apertureMode = AVAssetImageGeneratorApertureModeEncodedPixels;
    AVAssetImageGeneratorCompletionHandler generatorHandler =
    ^(CMTime requestedTime, CGImageRef im, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error){
        if (result == AVAssetImageGeneratorSucceeded) {
            UIImage *thumbImg = [UIImage imageWithCGImage:im];
            if (handler) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    handler(thumbImg);
                });
            }
        }
    };
    [generator generateCGImagesAsynchronouslyForTimes:
     [NSArray arrayWithObject:[NSValue valueWithCMTime:thumbTime]] completionHandler:generatorHandler];
}

//完成视频录制时调用
- (void)finishWithCompletionHandler:(void (^)(void))handler {
    [_writer finishWritingWithCompletionHandler: handler];
}

以上就是本博客内容的全部内容,大家如果有什么疑问可以问我,本文附带有demo,大家可以去看看具体怎么使用,有用的话可以点一下star,谢谢大家的阅读~~

我的demo地址

iOS捕捉图像 iOS媒体捕捉一、捕捉类介绍 捕捉会话AVCaptureSessionAV Foundation捕捉栈的核心类是AVCaptureSession,这个类相当与一个插排,用于连接输入和输出资源,捕捉会话还可以额外配置一个会话预设值(session preset)用来控制捕捉数据的格式和质量 捕捉设备AVCaptureDeviceAVCaptureDevice为摄像头或麦克风等物理设备定义了一个接口,它 阅读详情

相关推荐

iOS 自定义录制视频功能

大家都知道 iOS提供了一个系统录制使用的方法 UIImagePcikerController 他不仅有拍照的功能 还有录制视屏的功能。这个方法大家自己研究一下,我们今天主要讲一下自定义 录制视屏的方法。 我们使用AVCaptureSession 来自定义录制视屏的界面 大致过程如下 1,建立Session   2,添加input 3,添加output 4,开始捕捉 5,为用户显示

1万+

iOS 唤起相机session startRunning导致卡顿

1.苹果官方文档说明 - (void)startRunning; Discussion This method is used to start the flow of data from the inputs to the outputs connected to the AVCaptureSession instance that is the receiver. Thi...

Pandakingli的博客 7557

iOS开发系列--音频播放、录音、视频播放、拍照、视频录制

--iOS多媒体 概览 随着移动互联网的发展,如今的手机早已不是打电话、发短信那么简单了,播放音乐、视频、录音、拍照等都是很常用的功能。在iOS中对于多媒体的支持是非常强大的,无论是音视频播放、录制,还是对麦克风、摄像头的操作都提供了多套API。在今天的文章中将会对这些内容进行一一介绍: 音频 音效 音乐 音频会话 录音 音频队列服务 视频 MPMoviePlayerContr

Kenshin Cui's Blog 1万+

AVCaptureSession startRunning 函数应该在主线程调用还是子线程调用

AVCaptureSession的startRunning函数应该在主线程中调用。

weixin_35754962的博客 1485

iOS视频录制、压缩导出、取帧

首发博客:标哥的技术博客关注公众号:iOS开发技术分享概述花了点时间研究了一下常用的视频获取、录制、压缩并导出、保存到相册、取帧图功能,分享给大家了!相信阅读完本篇文章,会对你有很大的帮助的!记得关注微博、公众号哦,请到文章末尾跳转!本篇文章研究几下以个功能: 视频录制 从相册选择视频 保存视频到相册 获取视频帧图 压缩并导出视频 解决iOS8上录视频引起的偏移bug 需要真机测试,才能录制视频!效

标哥 5193

基于AVFoundation实现视频录制的两种方式

方案一:AVCaptureSession + AVCaptureMovieFileOutput 方案二:AVCaptureSession + AVAssetWriter

Rey的技术博客 1837

探索iOS开发新境界:使用AVFoundation自定义视频录制

探索iOS开发新境界:使用AVFoundation自定义视频录制 在iOS应用开发中,为用户提供丰富的媒体处理功能是提升用户体验的重要手段之一。AVFoundation框架,作为苹果官方提供的强大多媒体处理库,可以帮助我们实现高级的视频录制和编辑功能。今天,我们将向您推荐一个基于AVFoundation的开源项目,它将带您进入自定义视频录制的新世界。 1、项目介绍 该项目专注于教授开发者如何使用A...

gitblog_00010的博客 507

使用AVFoundation实现自定义视频录制功能的iOS高级教程

AVFoundation是苹果公司提供的一个强大的媒体处理框架,允许开发者通过高级的编程接口来操作音频和视频。在视频录制领域,它提供了从捕获、处理到播放视频的全方位支持。这一框架被广泛应用于iOS和macOS应用开发中,为开发者提供了强大的工具集合,使他们能够在应用中实现高质量的视频内容操作功能。AVCaptureSession是AVFoundation框架中用于视频和音频捕获的核心组件。它负责协调视频捕获设备、音频输入设备以及捕获数据的处理流程。

weixin_34471637的博客 883

(十)深入了解AVFoundation-采集:录制视频功能的实现

在本篇中,我们详细介绍了如何在 iOS 中实现高清视频与音频的录制。从配置 AVCaptureSession 和添加输入输出设备,到设置视频和音频的录制输出,我们逐步完成了录制会话的初始化和控制。特别是在开始与停止录制部分,我们展示了如何通过 AVCaptureMovieFileOutput 控制录制的启动与停止,并通过代理方法处理录制完成后的文件。接下来,你可以根据项目需求进一步优化视频录制体验,例如添加实时预览、录制过程中动态调整质量,或者对录制的文件进行后期处理(如视频编辑、转码等)。

weixin_39339407的博客 943

iOS中的视频录制视频采集

我们了解了如何使用 AVFoundation 创建一个自定义的视频录制页面,并实现视频的录制、重拍和上传功能。相比于系统提供的 UIImagePickerController 和 PHPickerViewController,自定义实现能够提供更多的灵活性和定制化选项,以满足特定项目的需求。

weixin_39339407的博客 1882

iOS平台视频播放器开发实践:基于AVFoundation

AVFoundation是一个强大的多媒体处理框架,它允许开发者在iOS和macOS应用程序中集成高质量的音视频功能。它提供了广泛的API来支持视频和音频的录制、播放、编辑以及转码。开发者可以根据需要调整视频显示区域的布局。例如,可以通过修改来控制视频内容在屏幕上的位置和大小。此外,还可以利用的变换属性来实现更复杂的视觉效果。在这个示例中,我们使用了变换属性transform将视频内容在 X 和 Y 方向上各移动了 50 个像素单位。这在创建响应式布局或添加过渡动画时非常有用。

weixin_35815766的博客 1452

iOS 视频录制、播放,使用视听媒体框架AVFoundation实现视频录制播放 —— HERO博客

用AVFoundation框架录制视频、AVPlayer播放视频。

hero_wqb的博客 4584

Infineon MOSFET开关损耗计算实战指南

本文是英飞凌MOSFET开关损耗计算的实战指南。针对MOSFET发热问题,文章深入剖析了开关损耗的产生机理,并基于Infineon数据手册的关键参数,手把手推导了开通与关断损耗的工程计算公式。同时,指南强调了栅极电阻、寄生参数等实际因素对损耗的影响,并提供了从理论计算到实测优化的完整路径,帮助工程师精准评估与优化电源设计。

weixin_29212867的博客 445

深入解析 iOS 视频录制(一):录制管理核心MWRecordingController 类的设计与实现

为了将结果和错误信息回调到调用的对象中,我们定义了一个 MWRecordingControllerDelegate 的代理,主要包含了录制发生错误的回调,以及录制完成的回调。/// 录制发生错误/// 录制完成通过本篇博客,我们深入解析了 MWRecordingController 类在 iOS 视频录制中的核心作用,详细介绍了如何配置会话、设置视频输入输出,以及录制控制的实现。这些技术为实现高效的视频录制功能奠定了基础。

weixin_39339407的博客 2173

SUMO仿真教程(5) —— 使用“XML“语言自定义构建路网

文章目录一.简要介绍1.node file2.edge file3.lane definitions(1)路段细分(2)邻近的对向车道(3)删除边或车道4.type file(1)简要概述(2)edge定义中引用type文件的示例(3)指定车辆种类的速度限制5.connections file(1)简要概述:(2)connection的属性参数(3)删除连接(4)设置连接的优先级(5)同一边的多个连接二.演示示例:1.自定义编写node file2.自定义编写edge file3.自定义编写type fil

weixin_50632459的博客 5017

抖音小程序开发 唤起收银台支付(可以选择支付宝APP支付或微信H5支付)

什么都别说,先看效果: 字节跳动也开放了小程序给商家接入,可以在旗下APP如抖音、今日头条、今日头条极速版等应用中即点即用,基于庞大的数亿用户量为其引流,帮助商家获取用户流量,销售商品,其模式和微信小程序差不多。 1、后台管理地址: https://developer.toutiao.com/ 2、开发文档地址: https://developer.toutiao.com/dev/cn/min...

悟空码字的博客 1万+

CameraKit iOS视频录制完全指南:从基础到高级的视频功能实现

想要在iOS应用中快速集成高质量视频录制功能吗?CameraKit iOS为你提供了一个简单高效的解决方案!这个强大的开源库能帮助你轻松实现专业级的视频录制功能,无需深入了解复杂的AVFoundation框架。无论你是iOS开发新手还是经验丰富的开发者,CameraKit都能大幅提升你的开发效率。🎥 ## 为什么选择CameraKit进行iOS视频录制? CameraKit是一个专门为iOS

gitblog_00866的博客 982

从零构建:STM32与EC800 4G模块的工业物联网数据透传实战

本文详细介绍了如何从零构建STM32与EC800 4G模块的工业物联网数据透传系统。内容涵盖硬件选型、抗干扰电路设计、AT指令稳定性优化、网络连接管理、可靠数据透传协议以及低功耗策略,为开发者提供了一套可直接应用于工业场景的完整实战方案。

uran的专栏 1014

iOS框架AVFoundation实现相机拍照、录制视频

在iOS开发中,使用AVFoundation框架实现相机拍照和录制视频的功能是一个常见的需求。AVFoundation提供了丰富的API来访问和控制设备的相机和麦克风。

lcfengokok的博客 994

光伏储能交直流微电网Matlab/Simulink仿真模型

首先是光伏储能交直流微电网,这个也是我还是之前在硕士期间在做光伏扶贫项目时候搭建的仿真,仿真由光伏、蓄电池及负载组成的独立直流微电网,提出电压和电流分段式协同控制策略。接下来我将主要更新关于光伏、风电、储能微电网系统相关的内容,包括具体建模和控制。

2401_89292556的博客 785

英飞凌Tricore问题排查01_Det/Reset/Trap排查宝典

软件跑飞进Det或发生Reset或进Trap了,怎么查?有固定套路可以follow吗?英飞凌Tricore又有哪些专门的定位办法?本篇为您娓娓道来。

initiallizer的博客 4417

iOS GPUImage使用 短视频录制 暂停录制 滤镜 自定义美颜滤镜 gif动态及文本水印添加 视频拼接

功能(有示例代码): 1.基于GPUImage自定义美颜滤镜 2.基于GPUImage添加文本水印及动态水印 3.录制过程中各种滤镜随意切换,及文本水印动态水印随意切换可以加载gif图作为水印 4.录制过程中可以暂停并继续录制 5.使用AVFoundation框架进行视频拼接 6.获取短视频第一帧图片,示例代码中有方法 录制页面: 示例代码:https://github.com/huizai07...

huizai的博客 3171

Figma 设计稿转前端代码:Cursor 实现 UI 还原的 4 种精准映射方法

揭示 Figma 设计稿转前端代码失效的根本原因在于四层语义未对齐(像素→布局、视觉→样式、交互→行为、意图→架构),提出以语义标注为核心的结构化喂数据方法,详解通过图层命名、Description 字段和 Markdown 上下文锚点实现高精度 React 组件生成,并给出可落地的避坑指南与实测提

weixin_42545292的博客 140

深入解析 iOS 视频录制(三):完整录制流程的实现与整合

在本篇博客中,我们详细介绍了如何在 ViewController 中实现完整的 iOS 视频录制功能。通过整合之前讨论的录制管理核心 MWRecordingController 和自定义的 UI 组件,我们构建了一个可交互的视频录制界面,涵盖了录制的各个方面:从视频预览、录制控制到视频保存与封面生成,提供了一个完整且流畅的用户体验。通过这次实现,大家可以了解到如何使用 AVFoundation 框架来处理视频录制,同时也掌握了如何结合自定义 UI 和交互设计,提升应用的易用性与功能性。

weixin_39339407的博客 1856

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

上一篇: iOS开发小结 - 通过PUT请求上传数据
下一篇: iOS开发进阶 - 实现类似微信和支付宝的密码输入框(UIKeyInput协议)
W_C__L
W_C__L 领域专家: 移动开发技术领域 领域专家: 移动开发技术领域
博客等级 码龄14年 194粉丝 68原创
评论 71
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值