iOS VideoToolbox硬编H.265(HEVC)H.264(AVC):1 概述
本文档尝试用Video Toolbox进行H.265(HEVC)硬件编码,视频源为iPhone后置摄像头。去年做完硬解H.264,没做编码,技能上感觉有些缺失。正好刚才发现CMFormatDescription.h中enum : CMVideoCodecType
提供了kCMVideoCodecType_HEVC
枚举值。所以呢,作死试试 iOS 9.2 硬编HEVC。
结论:不支持开发者使用H.265(HEVC),可以用H.264(AVC)。
1、读取iPhone后置摄像头
提示:iPhone不支持同时打开前后摄像头。因为SoC目前通常只有一个视频通道(Video Channel),当有两个AVCaptureSession先后运行,前一个会自动停止,后一个会继续运行。或者,有人想一个AVCaptureSession添加前后摄像头作为AVCaptureDeviceInput,这样会异常。因为两个办法我都试过。
iOS 8及后续版本,打开摄像头需要用户授权。
1.1、指定摄像头
我使用iPhone 6p当测试机,它有两个摄像头,要指定需使用的摄像头,在此使用后置摄像头当数据源。
AVCaptureDevice *avCaptureDevice;
NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in cameras) {
if (device.position == AVCaptureDevicePositionBack) {
avCaptureDevice = device;
}
}
若想直接使用后置摄像头,可简化上述代码。
AVCaptureDevice * avCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
1.2、打开摄像头
对于捕获摄像头,整个行为都由AVCaptureSession会话类维护,简化了编程复杂度。输入为摄像头,输出为用户需要的通道,如屏幕。
NSError *error = nil;
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:avCaptureDevice error:&error];
if (!videoInput) {
return;
}
AVCaptureSession *avCaptureSession = [[AVCaptureSession alloc] init];
avCaptureSession.sessionPreset = AVCaptureSessionPresetHigh; // sessionPreset为AVCaptureSessionPresetHigh,可不显式指定
[avCaptureSession addInput:videoInput];
配置好输入,现在配置输出,即摄像头的输出数据格式等。由AVCaptureDevice.formats可知当前设备支持的像素格式,对于iPhone 6,就两个默认格式:420f和420v。需要输出32BGRA,则需AVCaptureSession进行配置kCVPixelBufferPixelFormatTypeKey,已测可用值为
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
,即420vkCVPixelFormatType_420YpCbCr8BiPlanarFullRange
,即420fkCVPixelFormatType_32BGRA
,iOS在内部进行YUV至BGRA格式转换
YUV420一般用于标清视频,YUV422用于高清视频,这里的限制让人感到意外。但是,在相同条件下,YUV420计算耗时和传输压力比YUV422都小。
AVCaptureVideoDataOutput *avCaptureVideoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
NSDictionary*settings = @{(__bridge id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)};
avCaptureVideoDataOutput.videoSettings = settings;
dispatch_queue_t queue = dispatch_queue_create("com.github.michael-lfx.back_camera_io", NULL);
[avCaptureVideoDataOutput setSampleBufferDelegate:self queue:queue];
[avCaptureSession addOutput:avCaptureVideoDataOutput];
添加预览界面。
AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:avCaptureSession];
previewLayer.frame = self.view.bounds;
previewLayer.videoGravity= AVLayerVideoGravityResizeAspectFill;
[self.view.layer addSublayer:previewLayer];
启动会话。
[avCaptureSession startRunning];
启动应用可看到摄像头当前图像。
1.3、从回调中获取摄像头数据
默认情况下,iPhone 6p为30 fps,意味着如下函数每秒调用30次,那么,先简单打印摄像头输出数据的信息。
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
if (CVPixelBufferIsPlanar(pixelBuffer)) {
NSLog(@"kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange -> planar buffer");
}
CMVideoFormatDescriptionRef desc = NULL;
CMVideoFormatDescriptionCreateForImageBuffer(NULL, pixelBuffer, &desc);
CFDictionaryRef extensions = CMFormatDescriptionGetExtensions(desc);
NSLog(@"extensions = %@", extensions);
}
结果如下:
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange -> planar buffer
extensions = {
CVBytesPerRow = 2904;
CVImageBufferColorPrimaries = "ITU_R_709_2";
CVImageBufferTransferFunction = "ITU_R_709_2";
CVImageBufferYCbCrMatrix = "ITU_R_709_2";
Version = 2;
}
在我有限的视频基础中,ITU_R_709_2是HD视频的方案,一般用于YUV422,YUV至RGB的转换矩阵和SD视频(一般是ITU_R_601_4)并不相同。
CVPixelBufferGetPixelFormatType()可获取摄像头输出的像素数据格式,和前面指定的格式一致。
在当iPhone 6上运行且将sessionPreset设置为AVCaptureSessionPreset640x480,得到如下输出结果。
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange -> planar buffer
extensions = {
CVBytesPerRow = 964;
CVImageBufferColorPrimaries = "ITU_R_709_2";
CVImageBufferTransferFunction = "ITU_R_709_2";
CVImageBufferYCbCrMatrix = "ITU_R_601_4";
Version = 2;
}
分析一下CVBytesPerRow。CVBytesPerRow值964与CVPixelBufferGetBytesPerRow函数返回值一致。从预置可知,Y平面为640和CVPixelBufferGetWidth、CVPixelBufferGetWidthOfPlane(0)函数返回值一致。
CVPixelBufferGetBytesPerRow文档
The number of bytes per row of the image data. For planar buffers, this function returns a rowBytes value such that bytesPerRow * height covers the entire image, including all planes.
从上述文档可知CVPixelBufferGetBytesPerRow返回Planar缓冲区多个通道的宽度和,在此是Y、UV通道的宽度和:Y + U + V = 640 + (640/2 + 640/2) = 1280。当然,这个计算方式是错的。按YUV420采样规则计算,则一个像素点用8+2+2表示,即是,每个像素点12个位,那么每行图像实际拥有字节数为
640x12/8 = 960
与CVBytesPerRow不等。下面,再用理论公式计算图像的体积。
通过CVPixelBufferGetHeight得到高为480,图像体积为
640x480 + ((640/2) x (480/2)) + ((640/2) x (480/2))
=>640x480x3/2
=>460800
而CVPixelBufferGetDataSize返回462728,显然不相等。就像FFmpeg出于加速读取内存的目的,在AVFrame.data中加入填充数据,导致AVFrame.linesize >= AVFrame.width。那么,CVPixelBuffer是否存在行为呢?
size_t extraColumnsOnLeft;
size_t extraColumnsOnRight;
size_t extraRowsOnTop;
size_t extraRowsOnBottom;
CVPixelBufferGetExtendedPixels(pixelBuffer,
&extraColumnsOnLeft,
&extraColumnsOnRight,
&extraRowsOnTop,
&extraRowsOnBottom);
NSLog(@"extra (left, right, top, bottom) = (%ld, %ld, %ld, %ld)",
extraColumnsOnLeft,
extraColumnsOnRight,
extraRowsOnTop,
extraRowsOnBottom);
上述代码输出结果都为0,并无拓展像素。此问题留待解决。
2、VideoToolbox HEVC、AVC编码尝试
iOS支持硬编H.264(AVC)的Profile与Level描述在VTCompressionProperties.h,简单总结为:
Baseline
1 - 3
3 - [0, 2]
4 - [0, 2]
5 - [0, 2]
自动Profile、Level
Main
3 - [0, 2]
4 - [0, 2]
5 - [0, 2]
自动Profile、Level
Extended Main
5 - [0]
自动Profile、Level
High
3 - [0, 2]
4 - [0, 2]
5 - [0, 2]
自动Profile、Level
VideoToolbox编码算法如下:
创建编码会话
准备编码
逐帧编码
结束编码
2.1、创建编码会话
// 获取摄像头输出图像的宽高
size_t width = CVPixelBufferGetWidth(pixelBuffer);
size_t height = CVPixelBufferGetHeight(pixelBuffer);
static VTCompressionSessionRef compressionSession;
OSStatus status = VTCompressionSessionCreate(NULL,
width, height,
kCMVideoCodecType_H264,
NULL,
NULL,
NULL, &compressionOutputCallback, NULL, &compressionSession);
kCMVideoCodecType_H264
改成kCMVideoCodecType_HEVC
,在iOS 9.2.1 iPhone 6p、iPhone 6sp执行均返回错误-12908,kVTCouldNotFindVideoEncoderErr
,找不到编码器。看来iOS 9.2并不开放HEVC编码器。
编码回调函数定义如下:
static void compressionOutputCallback(void * CM_NULLABLE outputCallbackRefCon, void * CM_NULLABLE sourceFrameRefCon,
OSStatus status,
VTEncodeInfoFlags infoFlags,
CM_NULLABLE CMSampleBufferRef sampleBuffer ) {
if (status != noErr) {
NSLog(@"%s with status(%d)", __FUNCTION__, status);
return;
}
if (infoFlags == kVTEncodeInfo_FrameDropped) {
NSLog(@"%s with frame dropped.", __FUNCTION__);
return;
}
/* ------ 辅助调试 ------ */
CMFormatDescriptionRef fmtDesc = CMSampleBufferGetFormatDescription(sampleBuffer); CFDictionaryRef extensions = CMFormatDescriptionGetExtensions(fmtDesc); NSLog(@"extensions = %@", extensions);
CMItemCount count = CMSampleBufferGetNumSamples(sampleBuffer); NSLog(@"samples count = %d", count); /* ====== 辅助调试 ====== */
// 推流或写入文件
}
编码成功时输出如下信息:
extensions = {
FormatName = "H.264";
SampleDescriptionExtensionAtoms = {
avcC = <014d0028 ffe1000b 274d0028 ab603c01 13f2a001 000428ee 3c30>;
};
}
samples count = 1
采样数据为1,并不意味着slice数量为1。目前没找到输出多slice码流(多个I、P Slice)的参数配置。sampleBuffer的详细信息示例如下:
CMSampleBuffer 0x126e9fd80 retainCount: 1 allocator: 0x1a227cb68
invalid = NO
dataReady = YES
makeDataReadyCallback = 0x0
makeDataReadyRefcon = 0x0
formatDescription = <CMVideoFormatDescription 0x126e9fd50 [0x1a227cb68]> {
mediaType:'vide'
mediaSubType:'avc1'
mediaSpecific: {
codecType: 'avc1' dimensions: 1920 x 1080
}
extensions: {<CFBasicHash 0x126e9eae0 [0x1a227cb68]>{type = immutable dict, count = 2, entries =>
0 : <CFString 0x19dd523e0 [0x1a227cb68]>{contents = "SampleDescriptionExtensionAtoms"} = <CFBasicHash 0x126e9e090 [0x1a227cb68]>{type = immutable dict, count = 1, entries =>
2 : <CFString 0x19dd57c20 [0x1a227cb68]>{contents = "avcC"} = <CFData 0x126e9e1b0 [0x1a227cb68]>{length = 26, capacity = 26, bytes = 0x014d0028ffe1000b274d0028ab603c01 ... a001000428ee3c30} }
2 : <CFString 0x19dd52440 [0x1a227cb68]>{contents = "FormatName"} = H.264} } }
sbufToTrackReadiness = 0x0
numSamples = 1
sampleTimingArray[1] = {
{PTS = {196709596065916/1000000000 = 196709.596}, DTS = {INVALID}, duration = {INVALID}},
}
sampleSizeArray[1] = {
sampleSize = 5707,
}
sampleAttachmentsArray[1] = {
sample 0: DependsOnOthers = false
}
dataBuffer = 0x126e9fc50
为方便调试,可将H264文件写入文件,用VLC等工具分析,这是本系列文档第二篇:
iOS VideoToolbox硬编H.265(HEVC)H.264(AVC):2 H264数据写入文件。
下面介绍avcC的作用。
avcC
放入CFDictionaryRef
然后传递至CMVideoFormatDescriptionCreate
,创建视频格式描述,接着创建解码会话,开始解码。
由此也可发现,VideoToolbox编码输出为avcC格式,而且VideoToolbox也只支持avcC格式的H.264。如果从网络中得到Annex-B格式的H.264数据(一般称作H.264裸流或Elementary Stream),用CMVideoFormatDescriptionCreateFromH264ParameterSets
创建视频格式描述更方便,同时解码时需要将Annex-B转换成avcC,这也是WWDC2014 513 "direct access to media encoding and decoding"中说VideoToolbox只支持MP4容器装载的H.264数据的原因,就我所知,当写入MP4时,Annex-B使用的起始码(Start Code)会被写成长度(Length)。这就是VideoToolBox硬解最容易出问题的点,我去年做硬解花了很长时间就是因为不了解H.264相关知识,各种出错。
2.2、准备编码
开始编码前,可配置H.264 Profile、Level、帧间距等设置,它们最终体现在SPS、PPS,指导解码器进行解码操作。
VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Main_AutoLevel);
// 等等一系列属性
OSStatus status = VTCompressionSessionPrepareToEncodeFrames(compressionSession);
if (status != noErr) {
// FAILED.
}
本系列文档第二篇iOS VideoToolbox硬编H.265(HEVC)H.264(AVC):2 H264数据写入文件进一步解释SPS、PPS。
2.3、逐帧编码
编码前,一般会锁定像素缓冲区基位置,编码完解除。同时,需要指定显示时间戳和持续时间。
if(CVPixelBufferLockBaseAddress(pixelBuffer, 0) != kCVReturnSuccess) {
// FAILED.
}
CMTime presentationTimeStamp = CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer);
CMTime duration = CMSampleBufferGetOutputDuration(sampleBuffer);
status = VTCompressionSessionEncodeFrame(compressionSession, pixelBuffer, presentationTimeStamp, duration, NULL, pixelBuffer, NULL);
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
编码不像解码一样可以指定VTDecodeFrameFlags
为同步操作,所以编码的回调是异步的。异步虽然提高了代码运行效率,同时带来整理帧序等额外操作,让音频同步编码等操作变复杂。
2.4、结束编码
编码结束时,调用VTCompressionSessionCompleteFrames
停止编码并指示编码器如何处理已编码及待编码帧。
接着调用VTCompressionSessionInvalidate
结束会话,否则硬件容易异常,需要重启手机。
最后释放VTCompressionSession
。
3、讨论
WWDC2014 513 "direct access to media encoding and decoding" 提及了在实时要求不高的场合,编码用MultiPass可得到更好的效果。我并没尝试。
iOS VideoToolbox硬编H.265(HEVC)H.264(AVC):1 概述的更多相关文章
- iOS VideoToolbox硬编H.265(HEVC)H.264(AVC):2 H264数据写入文件
本文档为iOS VideoToolbox硬编H.265(HEVC)H.264(AVC):1 概述续篇,主要描述: CMSampleBufferRef读取实际数据 序列参数集(Sequence Para ...
- iOS VideoToolbox硬编H.265(HEVC)H.264(AVC):4 同步编码
本文档描述Video Toolbox实现同步编码的办法. Video Toolbox在头文件描述了编码方式为异步,实际开发中也确实为异步. This function may be called as ...
- H.265/HEVC Codec 编解码 (MP4 和 TS)
1. H.265/HEVC 播放器 1) VLC media player 2.1.3 (眼下不支持H.265 TS播放) 2)ffmpeg中的ffplay (如:ffplay hevc.ts) 3 ...
- vlc源码分析(六) 调用OpenMAX硬解码H.265
H.265(HEVC)编码格式能够在得到相同编码质量视频的前提下,使用相当于H.264(AVC)一半的存储容量,虽然H.265的算法复杂度比H.264高一个数量级,但是硬件水平在不断提高,因此H.26 ...
- EasyPlayerPro安卓流媒体播放器实现Android H.265硬解码流程
本文转自EasyDarwin团队成员John的博客:http://blog.csdn.net/jyt0551/article/details/74502627 H.265编码算法作为新一代视频编码标准 ...
- 我们解决了如何将视频转换为HEVC / H.265和AVC / H.264
LEADTOOLS Recognition Imaging SDK是精选的LEADTOOLS SDK功能集,旨在在企业级文档自动化解决方案中构建端到端文档成像应用程序,这些解决方案需要OCR,MICR ...
- 【省带宽、压成本专题】深入解析 H.265 编码模式,带你了解 Apple 全面推进 H.265 的原因
过去几年,又拍云一直在点播.直播等视频应用方面潜心钻研,取得了不俗的成果.我们结合点播.直播.短视频等业务中的用户场景,推出了"省带宽.压成本"系列文章,从编码技术.网络架构等角度 ...
- 音视频编解码技术(一):MPEG-4/H.264 AVC 编解码标准
一.H264 概述 H.264,通常也被称之为H.264/AVC(或者H.264/MPEG-4 AVC或MPEG-4/H.264 AVC) 1. H.264视频编解码的意义 H.264的出现就是为了创 ...
- 深入解析 H.265 编码模式,带你了解Apple全面推进H.265的原因
今天我们聊聊视频编码.视频文件亘古以来存在一个矛盾:高清画质和视频体积的冲突,相同编码标准下,视频更高清,视频体积更大.因此,应用更先进的视频编码标准,降低视频体积,可以大幅降低网站的流量消耗. 目前 ...
随机推荐
- 【转载】Android开发学习笔记:Intent的简介以及属性的详解
http://liangruijun.blog.51cto.com/3061169/634411/ 一.Intent的介绍 Intent的中文意思是“意图,意向”,在Android中提供了Intent ...
- 转:C# 获取磁盘及CPU的序列号
原文地址:http://www.cnblogs.com/stray521/archive/2010/08/06/1793647.html //获取磁盘序列号 try { System.Manageme ...
- (转)Android面试题
1. 下列哪些语句关于内存回收的说明是正确的? (b ) A. 程序员必须创建一个线程来释放内存 B.内存回收程序负责释放无用内存 C.内存回收程序允许程序员直接释放内存 D.内存回收程序可以在 ...
- jQuery插件的编写和使用 <思维导图>
以下是jQuery插件的编写和使用的思维导图,全屏观看,请点击:jQuery插件的编写和使用
- Apache windows多线程设置
# WinNT MPM # ThreadsPerChild: constant number of worker threads in the server process # MaxRequests ...
- 20141113--SQL 事务
---------------------触发器----------------------------- --触发器本质上还是一个存储过程,trigger --只不过不是通过exec调用执行,而是通 ...
- UI1_UITableViewHomeWork
// // AppDelegate.m // UI1_UITableViewHomeWork // // Created by zhangxueming on 15/7/14. // Copyrigh ...
- WCF之数据契约
从抽象层面看,WCF能够托管CLR类型(接口和类)并将它们公开为服务,也能够以本地CLR接口和类的方式使用服务.然而,CLR类型却属于.NET的特定技术.由于面向服务的一个核心原则就是在跨越服务边界时 ...
- 使用单用户模式破解Linux密码
使用单用户模式破解Linux密码 特别说明:在实际工作应用中,安装Linux操作系统必须设置装载口令,否则很容易被破解. 1.使用reboot指令重启Linux操作系统 2.在进入操作系统数秒时,单击 ...
- C语言实现基于投票规则的细胞自动机
算法介绍 首先我们先看一下“基于投票规则的细胞自动机”的定义: 基于投票规则的细胞自动机,实际上是具有如下限定条件的一种细胞自动机: 状态:0或1: 邻居:中心的3*3邻居: 规则:计数p表示中心的3 ...