前言

从本文开始逐渐学习iOS自带的多媒体处理框架,例如AVFoundation,VideoToolbox,CoreMedia,CoreVideo实现多媒体的处理,并且将实现方式以及效果和ffmpeg的方式做对比

APP会有这样的需求,录制一段音频或者一段视频,或者拍摄一张照片等等,AVFoundation提供了为我们提供了实现这些需求的接口。通过这些接口我们可以从设备获取指定格式的未压缩的音视频数据,然后又可以压缩之后保存到文件里面存储在本地或者在网络上传输

本文的目的:
1、熟悉AVFoundation中关于音频采集视频采集接口的使用
2、将采集到的音频或者视频压缩后保存在MP4文件中

采集相关流程

image.png

上图介绍了AVFoundation框架中关于采集相关的对象关系图,如下为具体对象的解释

要开启音视频采集,必须在项目配置文件Info.plist中添加NSMicrophoneUsageDescription音频使用权限和NSCameraUsageDescription相机使用权限

相关对象及函数介绍

  • 1、AVCaptureSession
    采集会话对象,用于管理采集的开始,结束等等操作,它一端连接着麦克风和摄像头等等输入设备对象接受他们提供的音视频数据,另一端连接着音频输出对象和视频输出对象通过他们向外界提供指定格式的音视频数据
  • 2、AVCaptureDevice
    代表着具体的采集音频或者视频的物理对象,例如麦克风,前后置摄像头等等

  • 3、AVCaptureDeviceInput
    采集输入对象,它是AVCaptureInput的实现子类,该对象被连接到AVCaptureSession之后就可以对其提供音频或者视频数据了,通过如下方法添加采集输入对象
    -(void)addInput:(AVCaptureInput *)input;

  • 4、AVCaptureVideoDataOutput
    视频输出对象,它被添加到AVCaptureSession之后就可以向外界提供采集好的视频数据了,同时通过该对象设置采集到的视频数据的格式(包括像素的格式,比如RGB的还是YUV的等等)

  • 5、AVCaptureAudioDataOutput
    音频输出对象,它被添加到AVCaptureSession之后就可以向外界提供采集好的音频数据了,该对象对于音频数据的格式设置比较少,如果想要更加详细的音频格式采集可以采用AudioUnit框架进行 参考我前面写的文章 AudioUnit录制音频+耳返(四)

  • 6、AVCaptureStillImageOutput
    原始照片输出对象,同样它也需要通过被添加到AVCaptureSession之后向外界提供采集好的原始照片

AVCaptureVideoDataOutput、AVCaptureAudioDataOutput、AVCaptureStillImageOutput三个对象都是AVCaptureOutput的具体实现子类,
通过-(void)addOutput:(AVCaptureOutput *)output方法被添加AVCaptureSession中去,然后由他们向外界提供数据

  • 7、CMSampleBufferRef
    此对象代表了采集到的音视频数据的一个结构体,它包含了音频或者视频相关的参数,这些参数包括音频参数(编码方式,采样率,采样格式,声道类型,声道数等等),视频参数(编码方式,宽高,颜色标准bt601/bt709,像素格式yuv还是RGB)

8 、- (void)startRunning;和-(void)stopRunning;
分别对应着采集开始和结束采集,他们一般都成对调用。备注:startRunning方法可能会花费1秒左右时间,它会阻塞当前线程,所以使用是要注意不能阻塞主线程

实现代码

主要实现的功能就是采集音频和1280x720的视频,视频采用h264方式编码,音频采用aac方式编码,最后保存到mp4中(当然也可以保存到mov中)

#import <Foundation/Foundation.h>@interface AVMuxer : NSObject/** 实现功能:接封装MP4文件然后再重新封装到MP4文件中,不重新进行编码*/
- (void)remuxer:(NSURL*)srcURL dstURL:(NSURL*)dstURL;/** 实现功能:将一个MP4文件转换成MOV文件*  视频编码方式由H264变成H265*  // 备注:ios 不支持mp3的编码*  // 备注:低端机型不支持H265编码*/
- (void)transcodec:(NSURL*)srcURL dstURL:(NSURL*)dstURL;
@end
#import "AVCapture.h"/** 要开启音视频采集,必须在项目配置文件Info.plist中添加NSMicrophoneUsageDescription音频使用权限*  和NSCameraUsageDescription相机使用权限*/
@interface AVCapture()<AVCaptureVideoDataOutputSampleBufferDelegate,AVCaptureAudioDataOutputSampleBufferDelegate>
{// 采集相关对象AVCaptureSession *session;AVCaptureVideoDataOutput *videoOutput;AVCaptureAudioDataOutput *audioOutput;// 封装相关对象AVAssetWriter *writer;AVAssetWriterInput *writerAudioInput;AVAssetWriterInput *WriterVideoInput;dispatch_queue_t acaptureQueue;dispatch_queue_t vcaptureQueue;dispatch_semaphore_t samaphore;BOOL firstsample;BOOL isWrite;AVFileType filetype;
}
@end
@implementation AVCapture
- (void)startCaptureToURL:(NSURL*)dstURL duration:(float)time fileType:(AVFileType)type
{acaptureQueue = dispatch_queue_create("acaptureQueue.com", DISPATCH_QUEUE_SERIAL);vcaptureQueue = dispatch_queue_create("vcaptureQueue.com", DISPATCH_QUEUE_SERIAL);samaphore = dispatch_semaphore_create(0);firstsample = YES;isWrite = YES;filetype = type;// 创建采集会话if (![self createCaptureSession]) {return;}// 开启音视频封装器的工作[self startAssetWriter:dstURL];//  备注,此方法会阻塞当前线程,所以最好放在子线程中调用,不要阻塞组现场[session startRunning];// time 秒后停止录制dispatch_after(dispatch_time(DISPATCH_TIME_NOW, time*NSEC_PER_SEC), dispatch_get_main_queue(), ^{[self->writerAudioInput markAsFinished];[self->WriterVideoInput markAsFinished];[self->writer finishWritingWithCompletionHandler:^{}];self->isWrite = NO;});dispatch_semaphore_wait(samaphore, DISPATCH_TIME_FOREVER);}- (BOOL)createCaptureSession
{/** AVCaptureSession 采集会话对象,它一头连接着输入对象(比如麦克风采集音频,摄像头采集视频)*  一头连接着输出对象向app提供采集好的原始音视频数据*  通过它管理采集的开始与结束*/session = [[AVCaptureSession alloc] init];// 设置视频采集的宽高参数[session setSessionPreset:AVCaptureSessionPreset1280x720];// 实际的音频采集物理设备对象,通过此对象来创建音频输入对象;备注postion一定要是AVCaptureDevicePositionUnspecified// 否则会返回nilAVCaptureDevice *micro = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInMicrophone mediaType:AVMediaTypeAudio position:AVCaptureDevicePositionUnspecified];// 通过音频物理设备对象来创建音频输入对象,如果前面micro为nil这里也不会崩溃,audioInput会返回nilAVCaptureDeviceInput *audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:micro error:nil];if (![session canAddInput:audioInput]) {    // audioInput为nil这里也不会崩溃,会放回NONSLog(@"can not add audioInput");return NO;}[session addInput:audioInput];// 实际的视频采集物理设备对象,这里选择后置摄像头AVCaptureDevice *camera = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInWideAngleCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack];// 通过视频物理设备对象创建视频输入对象AVCaptureDeviceInput *videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:camera error:nil];if (![session canAddInput:videoInput]) {NSLog(@"can not add video input");return NO;}[session addInput:videoInput];// 如果调用了[session startRunning]之后要想改变音视频输出对象配置参数,则必须调用[session beginConfiguration];和// [session commitConfiguration];才能生效。如果没有调用[session startRunning]则这两句代码可以不写[session beginConfiguration];// AVCaptureVideoDataOutput 创建视频输出对象,对象用于向外部输出视频数据,通过该对象设置向外部输入的视频数据格式// 比如像素格式(iOS只支持kCVPixelFormatType_32BGRA/kCVPixelFormatType_420YpCbCr8BiPlanarFullRange// kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange三种格式)videoOutput = [[AVCaptureVideoDataOutput alloc] init];NSDictionary *videoSettings = @{(id)kCVPixelBufferPixelFormatTypeKey:@(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)};[videoOutput setVideoSettings:videoSettings];// 当采集速度过快而处理速度跟不上时的丢弃策略,默认丢弃最新采集的视频。这里设置为NO,表示不丢弃缓存起来videoOutput.alwaysDiscardsLateVideoFrames = NO;[videoOutput setSampleBufferDelegate:self queue:vcaptureQueue];[session addOutput:videoOutput];// AVCaptureAudioDataOutput 创建音频输出对象audioOutput = [[AVCaptureAudioDataOutput alloc] init];[audioOutput setSampleBufferDelegate:self queue:acaptureQueue];[session addOutput:audioOutput];[session commitConfiguration];return YES;
}- (void)startAssetWriter:(NSURL*)dstURL
{NSError *error = nil;// AVAssetWriter 音视频写入对象管理器,通过该对象来控制写入的开始和结束以及管理音视频输入对象/** AVAssetWriter对象*  1、封装器对象,用于将音视频数据(压缩或未压缩数据)写入文件*  2、可以单独写入音频或视频,如果写入未压缩的音视频数据,AVAssetWriter内部会自动调用编码器进行编码*//** 遇到问题:调用startWriting提示Error Domain=AVFoundationErrorDomain Code=-11823 "Cannot Save"*  分析原因:如果封装器对应的文件已经存在,调用此方法时会提示这样的错误*  解决方案:调用此方法之前先删除已经存在的文件*/unlink([dstURL.path UTF8String]);writer = [AVAssetWriter assetWriterWithURL:dstURL fileType:AVFileTypeMPEG4 error:&error];if (error) {NSLog(@"create writer failer");return;}// 往封装器中添加音视频输入对象,每添加一个输入对象代表要往容器中添加一路流,一般添加一路视频流/** AVAssetWriterInput 对象*  用于将数据写入容器,可以写入压缩数据也可以写入未压缩数据,如果outputSettings为nil则代表对数据不做压缩处理直接写入容器,*  不为nil则代表对对数据按照指定格式压缩后写入容器**/// 方式一、手动创建参数,不推荐。设置不对容易出错,其它参数采用默认值
//    NSDictionary *videoSettings = @{
//        AVVideoCodecKey:AVVideoCodecH264,
//        AVVideoWidthKey:@(640),
//        AVVideoHeightKey:@(480)
//    };// 方式二、由苹果系统返回NSDictionary *videoSettings = [videoOutput recommendedVideoSettingsForAssetWriterWithOutputFileType:filetype];WriterVideoInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];WriterVideoInput.expectsMediaDataInRealTime = YES;// 将写入对象添加到封装器中[writer addInput:WriterVideoInput];/** 遇到问题:调用appendSampleBuffer写入音视频数据时出错*  分析原因:刚开始设置的AVEncoderBitRateKey码率为6400,太小,压缩可能要比较长时间,导致时间比较久就出错了*  解决方案:将码率设置为合适的值96000*/// 方式一、手动创建参数,不推荐。设置不对容易出错,其它参数采用默认值// 编码方式,采样格式,采样率AudioChannelLayout layout;bzero(&layout, sizeof(layout));layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;NSDictionary *audioSettings = @{AVFormatIDKey:@(kAudioFormatMPEG4AAC),AVChannelLayoutKey:[NSData dataWithBytes:&layout length:sizeof(layout)],AVNumberOfChannelsKey:@(1),AVSampleRateKey:@(44100),AVEncoderBitRateKey:@(96000)};// 方式二、由苹果系统返回
//    NSDictionary *audioSettings = [audioOutput recommendedAudioSettingsForAssetWriterWithOutputFileType:filetype];writerAudioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:audioSettings];writerAudioInput.expectsMediaDataInRealTime = YES;[writer addInput:writerAudioInput];BOOL reuslt = [writer startWriting];if (!reuslt) {NSLog(@"start writer %@",writer.error);}
}- (void)processSample:(CMSampleBufferRef)samplebuffer
{CMFormatDescriptionRef sampleFormat = CMSampleBufferGetFormatDescription(samplebuffer);CMMediaType mediatype = CMFormatDescriptionGetMediaType(sampleFormat);CMTime pts = CMSampleBufferGetPresentationTimeStamp(samplebuffer);if (mediatype == kCMMediaType_Video) {    // 视频if (firstsample) {firstsample = NO;[writer startSessionAtSourceTime:pts];}if (WriterVideoInput.readyForMoreMediaData) {BOOL reulst = [WriterVideoInput appendSampleBuffer:samplebuffer];NSLog(@"writer video %d",reulst);}} else if(mediatype == kCMMediaType_Audio) {    // 音频if (firstsample) {firstsample = NO;[writer startSessionAtSourceTime:pts];}if (writerAudioInput.readyForMoreMediaData) {BOOL reulst = [writerAudioInput appendSampleBuffer:samplebuffer];NSLog(@"writer audio %d",reulst);}} else {NSLog(@"其他数据");}
}- (void)captureOutput:(AVCaptureOutput *)output didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{NSLog(@"drop sample");
}/** 前面虽然定义了音视频采集的工作队列(且是一个串行队列),但是这个代理的线程是不固定的,随机的*/
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{if (!isWrite) {NSLog(@"结束录制...");dispatch_semaphore_signal(samaphore);return;}if (output == audioOutput) {// 获取音频数据的大小size_t size = CMSampleBufferGetTotalSampleSize(sampleBuffer);NSLog(@"audio thread %@ size %ld",[NSThread currentThread],size);} else if(output == videoOutput){// 获取视频数据的大小,视频数据大小不能通过上面CMSampleBufferGetTotalSampleSize获取CVImageBufferRef imagebuffer = CMSampleBufferGetImageBuffer(sampleBuffer);size_t size = CVPixelBufferGetDataSize(imagebuffer);NSLog(@"video thread %@ size %ld",[NSThread currentThread],size);} else {NSLog(@"unknown output");}[self processSample:sampleBuffer];
}
@end

上述代码运行起来还是比较流畅的,CPU消耗也不高,因为AVFoundation编码时采用的硬编码

遇到问题

  • 1、调用appendSampleBuffer写入音视频数据时出错
    分析原因:刚开始设置的AVEncoderBitRateKey码率为6400,太小,压缩可能要比较长时间,导致时间比较久就出错了
    解决方案:将码率设置为合适的值96000

项目地址

https://github.com/nldzsz/ffmpeg-demo

位于AVFoundation目录下文件AVCapture.h/AVCapture.m中

AVFoundation音视频采集(三)相关推荐

  1. IOS音视频(三)AVFoundation 播放和录音

    IOS音视频(三)AVFoundation 播放和录音 1. 音频理论知识 1.1 声音的物理性质 1.2 数字音频 1.2.1 采样.量化和编码 1.2.2 音频编码 1.3 音频编解码 2. 播放 ...

  2. iOS直播(二)GPUImage音视频采集

    上文中介绍了用AVFoundation实现音视频采集(https://blog.csdn.net/dolacmeng/article/details/81268622) ,开源的基于GPU的第三方图像 ...

  3. Android 音视频采集与软编码总结

    请尊重原创,转载请注明出处:http://blog.csdn.net/mabeijianxi/article/details/75807435(本文已在 "任玉刚" 微信公众号发布 ...

  4. Android端WebRTC本地音视频采集流程源码分析

    WebRTC源码版本为:org.webrtc:google-webrtc:1.0.32006 本文仅分析Java层源码,在分析之前,先说明一下一些重要类的基本概念. MediaSource:WebRT ...

  5. webrtc简单案例——音视频采集和播放

    webrtc简单案例--音视频采集和播放 目录 打开摄像头并将画面显示到页面 打开麦克风并在页面播放捕获的声音 同时打开摄像头和麦克风,并在页面显示画面和播放捕获的声音 1. 打开摄像头并将画面显示到 ...

  6. WebRTC音视频采集和播放示例及MediaStream媒体流解析

    WebRTC音视频采集和播放示例及MediaStream媒体流解析 目录 示例代码--同时打开摄像头和麦克风,并在页面显示画面和播放捕获的声音 API解析 mediaDevices MediaStre ...

  7. Android 音视频开发(三) -- Camera2 实现预览、拍照功能

    音视频 系列文章 Android 音视频开发(一) – 使用AudioRecord 录制PCM(录音):AudioTrack播放音频 Android 音视频开发(二) – Camera1 实现预览.拍 ...

  8. 【WebRTC---源码篇】(三)Windows/Linux音视频采集封装模块

    视频采集相关类图 DeviceInfo接口提供了设备枚举相关功能. NumberOfDevices枚举设备个数. GetDeviceName获取某个设备名称. GetCapability枚举某个设备所 ...

  9. windows下directShow音视频采集

    目录 前言 一.DirectShow简介 二.DirectShow视频采集主要流程介绍 1.DirectShow视频采集主要API介绍 三.视频采集软件设计框图 四.音频采集软件设计框图 <wi ...

  10. 音视频开发三:FFmpeg安装与常用命令

    前言:在不同平台下安装FFmpeg 1.mac系统下安装ffmpeg. 打开terminal,运行:brew install ffmpeg 安装完后,terminal中输入:ffmpeg,出现下面提示 ...

最新文章

  1. 情人节微信红包数据公布,你离海王与海后有多远...
  2. python 用途-Python在每个行业的用处
  3. 关于MySql数据库设计表与查询耗时分析
  4. oracle 排序的分析函数,Oracle分析函数用法详解
  5. python 发送邮件不显示附件_python3发送邮件(无附件)
  6. oracle 日期6,EF 6与Oracle - 如何加入日期字段?
  7. APUE Chapter 1 - UNIX System Overview
  8. ActionScript 3.0 学习笔记三
  9. Java线程之间通信
  10. Spark sql数据倾斜优化的一个演示案例
  11. 如何在Mac上将iCloud照片库备份到Mac硬盘?
  12. 机器学习笔记(十二):随机梯度下降
  13. 微信小程序架构图与开发
  14. 小白的JAVA学习笔记(六)---Object类,接口
  15. Fedora系统配置中文输入法
  16. 【模电】0010 正弦波产生电路(RC正弦波振荡电路)
  17. 联想计算机怎么改为光驱启动,联想笔记本光驱启动设置方法
  18. alert uuid does not exits. Dropping to a shell!
  19. STM32:Modbus-RTU通讯协议——CRC校验
  20. 3款养生保健粥护理肠胃保健康

热门文章

  1. STM32F412 串口接收不到数据的问题
  2. guass-jordan消元法求逆的原理
  3. Android 录屏+录音,原生代码,无需root权限,好用更好懂
  4. Andriod7.0之wifi开启流程(含流程框图及流程图)
  5. 无线路由器关掉dhcp服务器,无线路由器关闭dhcp会好不好
  6. spring boot内置容器性能比较(Jetty、Tomcat、Undertow)
  7. Google奥运会彩蛋,你确定不来参加一下吗?
  8. 生成开端原著小说词云
  9. PHP swoole解密,[讨论]php7.3如何解密swoole
  10. 计算机学院王乐君,自动化学院2019年研究生科技报告会安排-中国地质大学(武汉)自动化学院...