最简单的 iOS 推流代码,视频捕获,软编码 (faac,x264),硬编码(aac,h264),美颜,flv 编码,rtmp 协议,陆续更新代码解析,你想学的知识这里都有,愿意懂直播技术的同学快来看!!
源代码:https://github.com/hardman/AWLive
通过系统相机录制视频获取音视频数据,是推流的第一步. 源码中提供 2 种获取音视频数据的方法:一是使用系统自带接口;二是使用 GPUImage.
本篇首先介绍第一种.
网络上关于获取视频数据的代码有不少,但是为了方便代码阅读,这里简要介绍一下.
[注意] 请仔细阅读代码注释
相关代码入口
整套推流代码的入口:AWAVCaptureManager,它是根据参数创建上述 2 种获取数据方法的一个工厂类.
可以通过设置 captureType 来决定使用哪种数据获取方式.
AWAVCaptureManager 部分代码如下:
设置了 captureType 之后,直接可以通过 avCapture 获取到正确的捕获视频数据的对象了.
typedef enum : NSUInteger {
AWAVCaptureTypeNone,
AWAVCaptureTypeSystem,
AWAVCaptureTypeGPUImage,
} AWAVCaptureType;
@interface AWAVCaptureManager : NSObject
//视频捕获类型
@property (nonatomic, unsafe_unretained) AWAVCaptureType captureType;
@property (nonatomic, weak) AWAVCapture *avCapture;
//省略其他代码
......
@end
AWAVCapture 是一个虚基类(c++ 中的说法,不会直接产生对象,只用来继承的类,java 中叫做抽象类). 它的两个子类分别是 AWSystemAVCapture 和 AWGPUImageAVCapture.
这里使用了多态.
如果 captureType 设置的是 AWAVCaptureTypeSystem,avCapture 获取到的真实对象就是 AWSystemAVCapture 类型; 如果 captureType 设置的是 AWAVCaptureTypeGPUImage,avCapture 获取到的真实对象就是 AWGPUImageAVCapture 类型.
AWSystemAVCapture 类的功能只有一个:调用系统相机,获取音视频数据.
相机数据获取的方法
分为 3 步骤:
初始化输入输出设备.
创建 AVCaptureSession,用来管理视频与数据的捕获.
创建预览 UI. 还包括一些其他功能:
切换摄像头
更改 fps
在代码中对应的是 AWSystemAVCapture 中的 onInit 方法.只要初始化就会调用.
【注意】请仔细阅读下文代码中的注释 初始化输入设备
初始化输出设备
- (void) createCaptureDevice {
// 初始化前后摄像头
// 执行这几句代码后,系统会弹框提示:应用想要访问您的相机.请点击同意
// 另外iOS10 需要在info.plist中添加字段NSCameraUsageDescription.否则会闪退,具体请自行baidu.
NSArray * videoDevices = [AVCaptureDevice devicesWithMediaType: AVMediaTypeVideo];
self.frontCamera = [AVCaptureDeviceInput deviceInputWithDevice: videoDevices.firstObject error: nil];
self.backCamera = [AVCaptureDeviceInput deviceInputWithDevice: videoDevices.lastObject error: nil];
// 初始化麦克风
// 执行这几句代码后,系统会弹框提示:应用想要访问您的麦克风.请点击同意
// 另外iOS10 需要在info.plist中添加字段NSMicrophoneUsageDescription.否则会闪退,具体请自行baidu.
AVCaptureDevice * audioDevice = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeAudio];
self.audioInputDevice = [AVCaptureDeviceInput deviceInputWithDevice: audioDevice error: nil];
//省略其他代码
...
}
创建 captureSession
- (void) createOutput {
//创建数据获取线程
dispatch_queue_t captureQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//视频数据输出
self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
//设置代理,需要当前类实现protocol:AVCaptureVideoDataOutputSampleBufferDelegate
[self.videoDataOutput setSampleBufferDelegate: self queue: captureQueue];
//抛弃过期帧,保证实时性
[self.videoDataOutput setAlwaysDiscardsLateVideoFrames: YES];
//设置输出格式为 yuv420
[self.videoDataOutput setVideoSettings: @ { (__bridge NSString * ) kCVPixelBufferPixelFormatTypeKey: @ (kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)
}];
//音频数据输出
self.audioDataOutput = [[AVCaptureAudioDataOutput alloc] init];
//设置代理,需要当前类实现protocol:AVCaptureAudioDataOutputSampleBufferDelegate
[self.audioDataOutput setSampleBufferDelegate: self queue: captureQueue];
// AVCaptureVideoDataOutputSampleBufferDelegate 和 AVCaptureAudioDataOutputSampleBufferDelegate 回调方法名相同都是:
// captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
// 最终视频和音频数据都可以在此方法中获取.
}
创建预览 UI
// AVCaptureSession 创建逻辑很简单,它像是一个中介者,从音视频输入设备获取数据,处理后,传递给输出设备(数据代理/预览layer).
-(void) createCaptureSession{
//初始化
self.captureSession = [AVCaptureSession new];
//修改配置
[self.captureSession beginConfiguration];
//加入视频输入设备
if ([self.captureSession canAddInput:self.videoInputDevice]) {
[self.captureSession addInput:self.videoInputDevice];
}
//加入音频输入设备
if ([self.captureSession canAddInput:self.audioInputDevice]) {
[self.captureSession addInput:self.audioInputDevice];
}
//加入视频输出
if([self.captureSession canAddOutput:self.videoDataOutput]){
[self.captureSession addOutput:self.videoDataOutput];
[self setVideoOutConfig];
}
//加入音频输出
if([self.captureSession canAddOutput:self.audioDataOutput]){
[self.captureSession addOutput:self.audioDataOutput];
}
//设置预览分辨率
//这个分辨率有一个值得注意的点:
//iphone4录制视频时 前置摄像头只能支持 480*640 后置摄像头不支持 540*960 但是支持 720*1280
//诸如此类的限制,所以需要写一些对分辨率进行管理的代码.
//目前的处理是,对于不支持的分辨率会抛出一个异常
//但是这样做是不够,不完整的,最好的方案是,根据设备,提供不同的分辨率.
//如果必须要用一个不支持的分辨率,那么需要根据需求对数据和预览进行裁剪,缩放.
if (![self.captureSession canSetSessionPreset:self.captureSessionPreset]) {
@throw [NSException exceptionWithName:@"Not supported captureSessionPreset" reason:[NSString stringWithFormat:@"captureSessionPreset is [%@]", self.captureSessionPreset] userInfo:nil];
}
self.captureSession.sessionPreset = self.captureSessionPreset;
//提交配置变更
[self.captureSession commitConfiguration];
//开始运行,此时,CaptureSession将从输入设备获取数据,处理后,传递给输出设备.
[self.captureSession startRunning];
}
切换摄像头
// 其实只有一句代码:CALayer layer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
// 它其实是 AVCaptureSession的一个输出方式而已.
// CaptureSession会将从input设备得到的数据,处理后,显示到此layer上.
// 我们可以将此layer变换后加入到任意UIView中.
-(void) createPreviewLayer{
self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
self.previewLayer.frame = self.preview.bounds;
[self.preview.layer addSublayer:self.previewLayer];
}
设置 fps
-(void)setVideoInputDevice:(AVCaptureDeviceInput *)videoInputDevice{
if ([videoInputDevice isEqual:_videoInputDevice]) {
return;
}
//captureSession 修改配置
[self.captureSession beginConfiguration];
//移除当前输入设备
if (_videoInputDevice) {
[self.captureSession removeInput:_videoInputDevice];
}
//增加新的输入设备
if (videoInputDevice) {
[self.captureSession addInput:videoInputDevice];
}
//提交配置,至此前后摄像头切换完毕
[self.captureSession commitConfiguration];
_videoInputDevice = videoInputDevice;
}
获取音视频数据
- (void) updateFps: (NSInteger) fps {
//获取当前capture设备
NSArray * videoDevices = [AVCaptureDevice devicesWithMediaType: AVMediaTypeVideo];
//遍历所有设备(前后摄像头)
for (AVCaptureDevice * vDevice in videoDevices) {
//获取当前支持的最大fps
float maxRate = [(AVFrameRateRange * )[vDevice.activeFormat.videoSupportedFrameRateRanges objectAtIndex: 0] maxFrameRate];
//如果想要设置的fps小于或等于做大fps,就进行修改
if (maxRate >= fps) {
//实际修改fps的代码
if ([vDevice lockForConfiguration: NULL]) {
vDevice.activeVideoMinFrameDuration = CMTimeMake(10, (int)(fps * 10));
vDevice.activeVideoMaxFrameDuration = vDevice.activeVideoMinFrameDuration; [vDevice unlockForConfiguration];
}
}
}
}
至此,我们达到了所有目标:能够录制视频,预览,获取音视频数据,切换前后摄像头,修改捕获视频的 fps.
- (void) captureOutput: (AVCaptureOutput * ) captureOutput didOutputSampleBuffer: (CMSampleBufferRef) sampleBuffer fromConnection: (AVCaptureConnection * ) connection {
if (self.isCapturing) {
if ([self.videoDataOutput isEqual: captureOutput]) {
//捕获到视频数据,通过sendVideoSampleBuffer发送出去,后续文章会解释接下来的详细流程.
[self sendVideoSampleBuffer: sampleBuffer];
} else if ([self.audioDataOutput isEqual: captureOutput]) {
//捕获到音频数据,通过sendVideoSampleBuffer发送出去
[self sendAudioSampleBuffer: sampleBuffer];
}
}
}
文章列表
1 小时学会:最简单的 iOS 直播推流(一)项目介绍
1 小时学会:最简单的 iOS 直播推流(二)代码架构概述
1 小时学会:最简单的 iOS 直播推流(三)使用系统接口捕获音视频
1 小时学会:最简单的 iOS 直播推流(四)如何使用 GPUImage,如何美颜
1 小时学会:最简单的 iOS 直播推流(五)yuv,pcm 数据的介绍和获取
1 小时学会:最简单的 iOS 直播推流(六)h264,aac,flv 介绍
1 小时学会:最简单的 iOS 直播推流(七)h264/aac 硬编码
1 小时学会:最简单的 iOS 直播推流(八)h264/aac 软编码
1 小时学会:最简单的 iOS 直播推流(九)flv 编码与音视频时间戳同步
1 小时学会:最简单的 iOS 直播推流(十)librtmp 使用介绍
1 小时学会:最简单的 iOS 直播推流(十一)sps&pps 和 AudioSpecificConfig 介绍(完结)
来源: https://juejin.im/post/5a57273351882573450170d4