概述
AVFoundation 是一个可以用来使用和创建基于时间的视听媒体数据的框架。AVFoundation 的构建考虑到了目前的硬件环境和应用程序,其设计过程高度依赖多线程机制。充分利用了多核硬件的优势并大量使用block和GCD机制,将复杂的计算机进程放到了后台线程运行。会自动提供硬件加速操作,确保在大部分设备上应用程序能以最佳性能运行。该框架就是针对64位处理器设计的,可以发挥64位处理器的所有优势。
捕捉会话
AV Foundation 捕捉栈的核心类是AVCaptureSession。一个捕捉会话相当于一个虚拟的插线板,用于连接输入和输出的资源。捕捉会话管理从物理设备得到的数据流。捕捉会话可以额外配置一个会话预设(session preset),用于控制数据的捕捉和质量,会话的预设值为AVCaptureSessionPresetHigh。
self.captureSession = [[AVCaptureSession alloc] init];
[self.captureSession setSessionPreset:AVCaptureSessionPreset640x480];
捕捉设备
AVCaptureDevice为诸如摄像头或麦克风等物理设备等提供了一个接口。最常用的是+ (nullable AVCaptureDevice *)defaultDeviceWithMediaType:(AVMediaType)mediaType;
,根据指定的媒体类型返回系统默认的设备。
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
return;
}
[self.captureSession addInput:input];
捕捉输入输出
在使用捕捉设备进行处理前,首先要添加输入、输出设备。通常情况下AVCaptureDevice不能直接添加到AVCaptureSession中,我们需要使用AVCaptureDeviceInput。AVCaptureOutput是一个抽象基类,用于将捕捉到的数据输出。框架定义了AVCaptureOutput的一些扩展,具体如下所示:
类型 | 说明 |
---|---|
AVCaptureStillImageOutput | 图像输出,iOS10.0版本废弃,使用AVCapturePhotoOutput替代 |
AVCapturePhotoOutput | 图像输出,iOS10.0版本引入 |
AVCaptureVideoDataOutput | 视频输出 |
AVCaptureAudioDataOutput | 音频输出 |
AVCaptureFileOutput | 音视频文件输出,子类 AVCaptureMovieFileOutput、AVCaptureAudioFileOutput |
AVCaptureMetadataOutput | 元数据输出 |
捕捉预览
AVCaptureVideoPreviewLayer是CALayer的子类,可以对捕捉视频进行实时预览。它有个AVLayerVideoGravity
属性可以控制画面的缩放和拉升效果。
AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
previewLayer.frame = [UIScreen mainScreen].bounds;
[self.view.layer addSublayer:previewLayer];
H264编码
H264码流文件分为两层:
(1) VCL(Video Coding Layer)视频编码层:负责高效的视频内容表示,VCL 数据即编码处理的输出,它表示被压缩编码后的视频数据序列。
(2) NAL(Network Abstraction Layer)网络提取层:负责以网络所要求的恰当的方式对数据进行打包和传送,是传输层,不管是在本地播放还是在网络播放的传输,都要通过这一层来传输。
在H264协议里定义了三种帧,完整编码的帧叫I帧,参考之前的I帧生成的只包含差异部分编码的帧叫P帧,还有一种参考前后的帧编码的帧叫B帧。
在H264中图像以序列为单位进行组织,一个序列是一段图像编码后的数据流,以I帧开始,到下一个I帧结束。一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像。以下是三种帧的说明:
I 帧
I帧:帧内编码帧 ,I 帧表示关键帧,你可以理解为这一帧画面的完整保留;解码时只需要本帧数据就可以完成(因为包含完整画面)。I 帧特点:
- 它是一个全帧压缩编码帧。它将全帧图像信息进行JPEG压缩编码及传输;
- 解码时仅用I帧的数据就可重构完整图像;
- I帧描述了图像背景和运动主体的详情;
- I帧不需要参考其他画面而生成;
- I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量);
- I帧是帧组GOP的基础帧(第一帧),在一组中只有一个I帧;
- I帧不需要考虑运动矢量;
- I帧所占数据的信息量比较大。
P 帧
P 帧:前向预测编码帧。P 帧表示的是这一帧跟之前的一个关键帧(或P帧)的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。(也就是差别帧,P 帧没有完整画面数据,只有与前一帧的画面差别的数据)P 帧的预测与重构:P 帧是以I帧为参考帧,在I帧中找出P 帧“某点”的预测值和运动矢量,取预测差值和运动矢量一起传送。在接收端根据运动矢量从I帧中找出P帧“某点”的预测值并与差值相加以得到 P 帧“某点”样值,从而可得到完整的P 帧。P 帧特点:
- P帧是I帧后面相隔1~2帧的编码帧;
- P帧采用运动补偿的方法传送它与前面的I或P帧的差值及运动矢量(预测误差);
- 解码时必须将I帧中的预测值与预测误差求和后才能重构完整的P帧图像;
- P帧属于前向预测的帧间编码。它只参考前面最靠近它的I帧或P帧;
- P帧可以是其后面P帧的参考帧,也可以是其前后的B帧的参考帧;
- 由于P帧是参考帧,它可能造成解码错误的扩散;
- 由于是差值传送,P帧的压缩比较高。
B帧
B帧:双向预测内插编码帧。B帧是双向差别帧,也就是B帧记录的是本帧与前后帧的差别(具体比较复杂,有4种情况),换言之,要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。B帧压缩率高,但是解码时CPU会比较累。B帧特点:
1)B帧是由前面的I或P帧和后面的P帧来进行预测的;
2)B帧传送的是它与前面的I或P帧和后面的P帧之间的预测误差及运动矢量;
3)B帧是双向预测编码帧;
4)B帧压缩比最高,因为它只反映两参考帧间运动主体的变化情况,预测比较准确;
5)B帧不是参考帧,不会造成解码错误的扩散。
NAL Units
在 VCL 数据传输或存储之前,这些编码的 VCL 数据,先被映射或封装进NAL 单元中。每个 NAL 单元包括一个原始字节序列负荷( RBSP, Raw Byte Sequence Payload)和一组对应于视频编码的 NAL 头信息。RBSP 的基本结构是:在原始编码数据的后面填加了结尾比特。一个 bit“1”若干比特“0”,以便字节对齐。
NAL头由一个字节组成 ,如图2所示。
语法:禁止位(1bit)、重要性位(2bit)、NALU类型(5bit)。
NAL头信息的每一位说明如下
位 | 简称 | 全称 | 中文 | 作用 |
---|---|---|---|---|
0 | F | forbidden_zero_bit | 禁止位 | 网络发现NAL单元有比特错误时可设置该比特为1,以便接收方丢掉该单元 |
1-2 | NRI | nal_ref_idc | 重要性指示位 | 标志该NAL单元用于重建时的重要性,值越大,越重要。取 00 ~ 11 |
3-7 | TYPE | nal_unit_type | NALU类型 | 1 ~ 23表示单个NAL包,24 ~ 31需要分包或者组合发送,具体含义需要参考下面的表格 |
常见的NAL类型
NAL类型 | 描述 |
---|---|
1 | 不分区,非IDR图像的片 |
2 | 片分区A |
3 | 片分区B |
4 | 片分区C |
5 | IDR图像中的片 |
6 | 补充增强信息单元(SEI) |
7 | SPS(Sequence Parameter Set序列参数集,作用于一串连续的视频图像,即视频序列) |
8 | PPS(Picture Parameter Set图像参数集,作用于视频序列中的一个或多个图像) |
9 | 序列结束 |
10 | 序列结束 |
11 | 码流结束 |
12 | 填充 |
Profile
H.264有四种画质级别,分别是BP、EP、MP、HP:
Profile | 描述 |
---|---|
BP(Baseline profile) | 提供I/P帧,仅支持Progressive(逐行扫描)和CAVLC。多应用于“视频会话”,如可视电话、会议电视、远程教学、视频监控等实时通信领域; |
EP(Extended profile) | 提供I/P/B/SP/SI帧,仅支持Progressive和CAVLC。多应用于流媒体领域,如视频点播、基于网络的视频监控等; |
MP(Main profile) | 提供I/P/B帧,支持Progressive和Interlaced(隔行扫描),提供CAVLC和CABAC。多应用于数字电视广播、数字视频存储等领域; |
HiP(High profile) | (Fidelity Range Extensions,FRExt) 在Main profile基础上新增8*8帧内预测,Custom Quant,Lossless Video Coding,更多YUV格式(4:2:2,4:4:4),像素精度提高到10位或14位。多应用于对高分辨率和高清晰度有特别要求的领域。 |
颜色空间
Profile | 描述 |
---|---|
X264_CSP_I420 | yuv 4:2:0 planar |
X264_CSP_YV12 | yvu 4:2:0 planar |
X264_CSP_NV12 | yuv 4:2:0, with one y plane and one packed u+v |
X264_CSP_NV21 | yuv 4:2:0, with one y plane and one packed v+u |
X264_CSP_I422 | yuv 4:2:2 planar |
X264_CSP_YV16 | yvu 4:2:2 planar |
X264_CSP_NV16 | yuv 4:2:2, with one y plane and one packed u+v |
X264_CSP_V210 | 10-bit yuv 4:2:2 packed in 32 |
X264_CSP_I444 | yuv 4:4:4 planar |
X264_CSP_YV24 | yvu 4:4:4 planar |
X264_CSP_BGR | packed bgr 24bits |
X264_CSP_BGRA | packed bgr 32bits |
X264_CSP_RGB | packed rgb 24bits |
视频码率
视频码率是视频数据(视频色彩量、亮度量、像素量)每秒输出的位数。一般用的单位是kbps。
在视频会议应用中,视频质量和网络带宽占用是矛盾的,通常情况下视频流占用的带宽越高则视频质量也越高;如要求高质量的视频效果,那么需要的网络带宽也越大;解决这一矛盾的钥匙当然是视频编解码技术。评判一种视频编解码技术的优劣,是比较在相同的带宽条件下,哪个视频质量更好;在相同的视频质量条件下,哪个占用的网络带宽更少。
一套设置码率的公式
项目 | 计算公式 | 192x144 | 320x240 | 480x360 | 640x480 | 1280x720 | 1920x1080 |
---|---|---|---|---|---|---|---|
极低码率 | (宽x高x3)/4 | 30kb/s | 60kb/s | 120kps | 250kbps | 500kbps | 1mbps |
低码率 | (宽x高x3)/2 | 60kb/s | 120kb/s | 250kbps | 500kbps | 1mbps | 2mbps |
中码率 | (宽x高x3) | 120kb/s | 250kb/s | 500kbps | 1mbps | 2mbps | 4mbps |
高码率 | (宽x高x3)x 2 | 250kb/s | 500kb/s | 1mbps | 2mbps | 4mbps | 8mps |
极高码率 | (宽x高x3)x4 | 500kb/s | 1mb/s | 2mbps | 4mbps | 8mbps | 16mbps |
硬编码
硬编码是系统提供的,由系统专门嵌入的硬件设备处理音视频编码,主要计算操作在对应的硬件中。硬编码的特点是,速度快,CPU占用少,但是不够灵活,只能使用一些特定的功能。
- 初始化编码器。
- (void)setupEncoder:(int)width height:(int)height
{
_videoQueue = dispatch_queue_create("com.qm.video.encode", NULL);
dispatch_sync(_videoQueue, ^{
// 编码类型:kCMVideoCodecType_H264
// 编码回调:didCompressH264,这个回调函数为编码结果回调,编码成功后,会将数据传入此回调中。
OSStatus status = VTCompressionSessionCreate(NULL,
width,
height,
kCMVideoCodecType_H264,
NULL,
NULL,
NULL,
didCompressH264,
(__bridge void *)(self),
&EncodingSession);
NSLog(@"H264: VTCompressionSessionCreate %d", (int)status);
if (status != noErr)
{
NSLog(@"H264: Unable to create a H264 session");
return ;
}
// 设置实时编码
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
// ProfileLevel,h264的协议等级,不同的清晰度使用不同的ProfileLevel。
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Main_AutoLevel);
// 设置码率
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(width * height * 3));
// 关闭重排Frame,因为有了B帧(双向预测帧,根据前后的图像计算出本帧)后,编码顺序可能跟显示顺序不同。此参数可以关闭B帧
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse);
// 关键帧最大间隔,关键帧也就是I帧。此处表示关键帧最大间隔为2s。
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFTypeRef)@(25 * 2));
// Tell the encoder to start encoding
VTCompressionSessionPrepareToEncodeFrames(EncodingSession);
});
}
- 硬编码NV12数据。
- (void)encodeBuffer:(CMSampleBufferRef )sampleBuffer
{
dispatch_sync(_videoQueue, ^{
_frameCount++;
// 获取 CVImageBufferRef
CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
// 设置 pts
CMTime presentationTimeStamp = CMTimeMake(_frameCount, 1000);
VTEncodeInfoFlags flags;
// 编码
OSStatus statusCode = VTCompressionSessionEncodeFrame(EncodingSession,
imageBuffer,
presentationTimeStamp,
kCMTimeInvalid,
NULL, NULL, &flags);
// 错误处理
if (statusCode != noErr) {
NSLog(@"H264: VTCompressionSessionEncodeFrame failed with %d", (int)statusCode);
// End the session
VTCompressionSessionInvalidate(EncodingSession);
CFRelease(EncodingSession);
EncodingSession = NULL;
return;
}
NSLog(@"H264: VTCompressionSessionEncodeFrame Success");
});
}
- 处理编码回调。
static void didCompressH264(void *outputCallbackRefCon,
void *sourceFrameRefCon,
OSStatus status,
VTEncodeInfoFlags infoFlags,
CMSampleBufferRef sampleBuffer )
{
NSLog(@"didCompressH264 called with status %d infoFlags %d", (int)status, (int)infoFlags);
if (status != 0) return;
if (!CMSampleBufferDataIsReady(sampleBuffer))
{
NSLog(@"didCompressH264 data is not ready ");
return;
}
H264HwEncoder* encoder = (__bridge H264HwEncoder*)outputCallbackRefCon;
// 检查是否为关键帧
bool keyframe = !CFDictionaryContainsKey( (CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0)), kCMSampleAttachmentKey_NotSync);
if (keyframe)
{
CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);
size_t sparameterSetSize, sparameterSetCount;
const uint8_t *sparameterSet;
OSStatus statusCode = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &sparameterSet, &sparameterSetSize, &sparameterSetCount, 0 );
if (statusCode == noErr)
{
// 获取 sps
size_t pparameterSetSize, pparameterSetCount;
const uint8_t *pparameterSet;
OSStatus statusCode = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &pparameterSet, &pparameterSetSize, &pparameterSetCount, 0 );
if (statusCode == noErr)
{
// 获取 pps
NSData *sps = [NSData dataWithBytes:sparameterSet length:sparameterSetSize];
NSData *pps = [NSData dataWithBytes:pparameterSet length:pparameterSetSize];
if (encoder->_delegate)
{
[encoder->_delegate gotSpsPps:sps pps:pps starCode:NO];
}
}
}
}
// 获取其它视频帧
CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
size_t length, totalLength;
char *dataPointer;
OSStatus statusCodeRet = CMBlockBufferGetDataPointer(dataBuffer, 0, &length, &totalLength, &dataPointer);
if (statusCodeRet == noErr) {
size_t bufferOffset = 0;
static const int AVCCHeaderLength = 4;
// 一般情况下都是只有1帧,在最开始编码的时候有2帧,取最后一帧
while (bufferOffset < totalLength - AVCCHeaderLength) {
// 获取 NAL unit 数据长度
uint32_t NALUnitLength = 0;
memcpy(&NALUnitLength, dataPointer + bufferOffset, AVCCHeaderLength);
// Convert the length value from Big-endian to Little-endian
NALUnitLength = CFSwapInt32BigToHost(NALUnitLength);
NSData* data = [[NSData alloc] initWithBytes:(dataPointer + bufferOffset + AVCCHeaderLength) length:NALUnitLength];
// 如果保存到文件中,需要将此数据前加上 [0 0 0 1] 4个字节,按顺序写入到h264文件中。
[encoder->_delegate gotEncodedData:data isKeyFrame:keyframe starCode:NO];
// 转到下一个 NAL unit
bufferOffset += AVCCHeaderLength + NALUnitLength;
}
}
}
- 释放资源。
- (void)destroy
{
// Mark the completion
VTCompressionSessionCompleteFrames(EncodingSession, kCMTimeInvalid);
// End the session
VTCompressionSessionInvalidate(EncodingSession);
CFRelease(EncodingSession);
EncodingSession = NULL;
}
软编码
软编码是指通过CPU计算进行数据编码,主要计算操作在CPU中。软编码的特点是,灵活,多样,功能丰富可扩展,但是CPU占用较多。在这里软编码使用的是 x264 http://www.videolan.org/developers/x264.html
- 初始化编码器。
- (BOOL)setupEncoderWithWidth:(int)width height:(int)height frameRate:(int)fps bitrate:(int)bitrate
{
_width = width;
_height = height;
x264_param_t param;
//x264_param_default_preset 设置
x264_param_default_preset(¶m,"ultrafast","zerolatency");
//编码输入的像素格式YUV420P
param.i_csp = X264_CSP_NV12;
param.i_width = width;
param.i_height = height;
//参数i_rc_method表示码率控制,CQP(恒定质量),CRF(恒定码率),ABR(平均码率)
//恒定码率,会尽量控制在固定码率
param.rc.i_rc_method = X264_RC_CRF;
param.rc.i_bitrate = bitrate / 1000; //* 码率(比特率,单位Kbps)
param.rc.i_vbv_max_bitrate = bitrate / 1000 * 1.2; //瞬时最大码率
//码率控制不通过timebase和timestamp,而是fps
param.b_vfr_input = 0;
param.i_fps_num = fps; //* 帧率分子
param.i_fps_den = 1; //* 帧率分母
param.i_timebase_den = param.i_fps_num;
param.i_timebase_num = param.i_fps_den;
param.i_threads = 1;//并行编码线程数量,0默认为多线程
//是否把SPS和PPS放入每一个关键帧
//SPS Sequence Parameter Set 序列参数集,PPS Picture Parameter Set 图像参数集
//为了提高图像的纠错能力
param.b_repeat_headers = 1;
//设置Level级别
param.i_level_idc = 51;
//设置Profile档次
//baseline级别,没有B帧
x264_param_apply_profile(¶m,"baseline");
_x264Handle = x264_encoder_open(¶m);
if(_x264Handle == NULL) {
return NO;
}
_pPicIn = malloc(sizeof(x264_picture_t));
x264_picture_alloc(_pPicIn, X264_CSP_NV12, _width, _height);
_pPicIn->img.i_csp = X264_CSP_NV12;
_pPicIn->img.i_plane = 2;
_pPicOut = malloc(sizeof(x264_picture_t));
x264_picture_init(_pPicOut);
return YES;
}
- 编码NV12数据。
- (void)encodeBuffer:(CMSampleBufferRef)sampler
{
CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampler);
//表示开始操作数据
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
//Y数据
uint8_t *yFrame = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
// UV数据
uint8_t *uvFrame = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
//* 编码需要的辅助变量
int iNal = 0;
x264_nal_t *pNals = NULL;
_pPicIn->img.i_csp = X264_CSP_NV12;
_pPicIn->img.i_plane = 2;
_pPicIn->img.plane[0] = yFrame;
_pPicIn->img.plane[1] = uvFrame;
int frame_size = x264_encoder_encode(_x264Handle,
&pNals,
&iNal,
_pPicIn,
_pPicOut);
_pPicIn->i_pts += 1;
_pPicIn->i_dts += 1;
if(frame_size > 0) {
for (int i = 0; i < iNal; i++) {
NSLog(@"%d", pNals[i].i_type);
// SPS
if (pNals[i].i_type == NAL_SPS) {
NSData *sps = [NSData dataWithBytes:pNals[i].p_payload length:pNals[i].i_payload];
[_delegate gotSpsPps:sps pps:nil starCode:YES];
// PPS
}else if(pNals[i].i_type == NAL_PPS) {
NSData *pps = [NSData dataWithBytes:pNals[i].p_payload length:pNals[i].i_payload];
[_delegate gotSpsPps:nil pps:pps starCode:YES];
}else {
NSData *data = [NSData dataWithBytes:pNals[i].p_payload length:pNals[i].i_payload];
[_delegate gotEncodedData:data isKeyFrame:NO starCode:YES];
}
}
}
// Unlock
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
}
- 释放资源
- (void)destroy
{
//* 清除图像区域
x264_picture_clean(_pPicIn);
x264_picture_clean(_pPicOut);
//* 关闭编码器句柄
x264_encoder_close(_x264Handle);
_x264Handle = NULL;
}
使用编码器
分别使用软编码、硬编码编码NV12视频数据,将编码的h264文件保存到沙盒的Document目录中,然后用VLC播放器可以直接播放。注意如果保存到文件中,需要将此数据前加上 [0x00 0x00 0x00 0x01] 4个字节,按顺序写入到h264文件中,但是软编码已经加了[0x00 0x00 0x00 0x01],因此不用主动添加。
//
// ViewController.m
// Movie
//
// Created by qinmin on 2017/6/30.
// Copyright © 2017年 qinmin. All rights reserved.
//
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "H264HwEncoder.h"
#import "X264Utils.h"
#define kDocumentPath(path) [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:path]
@interface ViewController () <AVCaptureVideoDataOutputSampleBufferDelegate,H264HwEncoderDelegate,X264Delegate>
@property (nonatomic, strong) AVCaptureSession *captureSession;
@property (nonatomic, strong) AVCaptureVideoDataOutput *videoOutput;
@property (nonatomic, strong) H264HwEncoder *h264HwEncoder;
@property (nonatomic, strong) X264Utils *x264Encoder;
@property (nonatomic, assign) FILE *fileHandle;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupEncoder1];
[self setupOutputFile];
[self setupCaptureSession];
}
- (void)setupOutputFile
{
[[NSFileManager defaultManager] removeItemAtPath:kDocumentPath(@"output.h264") error:nil];
_fileHandle = fopen([kDocumentPath(@"output.h264") UTF8String], "ab+");
}
- (void)setupEncoder
{
self.h264HwEncoder = [[H264HwEncoder alloc] init];
[self.h264HwEncoder setupEncoder:640 height:480];
[self.h264HwEncoder setDelegate:self];
}
- (void)setupEncoder1
{
self.x264Encoder = [[X264Utils alloc] init];
[self.x264Encoder setupEncoderWithWidth:640 height:480 frameRate:25 bitrate:640*480*3];
[self.x264Encoder setDelegate:self];
}
- (void)setupCaptureSession
{
self.captureSession = [[AVCaptureSession alloc] init];
[self.captureSession setSessionPreset:AVCaptureSessionPreset640x480];
// Create a device input with the device and add it to the session.
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
return;
}
[self.captureSession addInput:input];
// Create a VideoDataOutput and add it to the session
_videoOutput = [[AVCaptureVideoDataOutput alloc] init];
[_videoOutput setSampleBufferDelegate:self queue:dispatch_get_global_queue(0, 0)];
_videoOutput.videoSettings = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey];
[self.captureSession addOutput:_videoOutput];
AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
previewLayer.frame = [UIScreen mainScreen].bounds;
[self.view.layer addSublayer:previewLayer];
// Start the session running to start the flow of data
[self.captureSession startRunning];
}
#pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection
{
if (!self.captureSession.isRunning) {
return;
}else if (captureOutput == _videoOutput) {
//CVImageBufferRef cameraFrame = CMSampleBufferGetImageBuffer(sampleBuffer);
//int bufferWidth = (int) CVPixelBufferGetWidth(cameraFrame);
//int bufferHeight = (int) CVPixelBufferGetHeight(cameraFrame);
[self.h264HwEncoder encodeBuffer:sampleBuffer];
[self.x264Encoder encodeBuffer:sampleBuffer];
}
}
- (void)gotSpsPps:(NSData *)sps pps:(NSData *)pps starCode:(BOOL)flag
{
char slide[] = "\x00\x00\x00\x01";
if (sps) {
if (!flag) {
fwrite(slide, 1, 4, _fileHandle);
}
fwrite(sps.bytes, 1, sps.length, _fileHandle);
}
if (pps) {
if (!flag) {
fwrite(slide, 1, 4, _fileHandle);
}
fwrite(pps.bytes, 1, pps.length, _fileHandle);
}
}
- (void)gotEncodedData:(NSData *)data isKeyFrame:(BOOL)isKeyFrame starCode:(BOOL)flag
{
if (!flag) {
char slide[] = "\x00\x00\x00\x01";
fwrite(slide, 1, 4, _fileHandle);
}
fwrite(data.bytes, 1, data.length, _fileHandle);
}
@end
参考
AVFoundation开发秘籍:实践掌握iOS & OSX应用的视听处理技术
源码地址:AVFoundation开发 https://github.com/QinminiOS/AVFoundation
最后
如果需要自己渲染视频视频可以参考之前的文章:
OpenGL ES入门11-相机视频渲染
GPUImage源码阅读(五)