硬编码相对于软编码来说,使用非CPU进行编码,如显卡GPU、专用的DSP、FPGA、ASIC芯片等,性能高,对CPU没有压力,但是对其他硬件要求较高(如GPU等)。

  在iOS8之后,苹果开放了接口,并且封装了VideoToolBox&AudioToolbox两个框架,分别用于对视频&音频进行硬编码,音频编码放在后面做总结,这次主要总结VideoToolBox。

  Demo的Github地址:https://github.com/wzpziyi1/HardCoding-For-iOS

  1、相关基础数据结构:

    CVPixelBuffer:编码前和解码后的图像数据结构。

    CMTime、CMClock和CMTimebase:时间戳相关。时间以64-bit/32-bit的形式出现。

    CMBlockBuffer:编码后,结果图像的数据结构。

    CMVideoFormatDescription:图像存储方式,编解码器等格式描述。

    CMSampleBuffer:存放编解码前后的视频图像的容器数据结构。

    

    

    如图所示,编解码前后的视频图像均封装在CMSampleBuffer中,如果是编码后的图像,以CMBlockBuffe方式存储;解码后的图像,以CVPixelBuffer存储。CMSampleBuffer里面还有另外的时间信息CMTime和视频描述信息CMVideoFormatDesc。
 
    代码A:
      

// 编码完成回调
void finishCompressH264Callback(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer)
{
if (status != noErr) return; //根据传入的参数获取对象
ZYVideoEncoder *encoder = (__bridge ZYVideoEncoder *)(outputCallbackRefCon); //判断是否是关键帧
bool isKeyFrame = !CFDictionaryContainsKey( (CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), )), kCMSampleAttachmentKey_NotSync); //如果是关键帧,获取sps & pps数据
if (isKeyFrame)
{
CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer); //获取sps信息
size_t sparameterSetSize, sparameterSetCount;
const uint8_t *sparameterSet;
CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, , &sparameterSet, &sparameterSetSize, &sparameterSetCount, ); // 获取PPS信息
size_t pparameterSetSize, pparameterSetCount;
const uint8_t *pparameterSet;
CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, , &pparameterSet, &pparameterSetSize, &pparameterSetCount, ); // 装sps/pps转成NSData,以方便写入文件
NSData *sps = [NSData dataWithBytes:sparameterSet length:sparameterSetSize];
NSData *pps = [NSData dataWithBytes:pparameterSet length:pparameterSetSize]; // 写入文件
[encoder gotSpsPps:sps pps:pps];
} //获取数据块
CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
size_t length, totalLength;
char *dataPointer;
OSStatus statusCodeRet = CMBlockBufferGetDataPointer(dataBuffer, , &length, &totalLength, &dataPointer); if (statusCodeRet == noErr)
{
size_t bufferOffset = ;
// 返回的nalu数据前四个字节不是0001的startcode,而是大端模式的帧长度length
static const int AVCCHeaderLength = ; //循环获取nalu数据
while (bufferOffset < totalLength - AVCCHeaderLength)
{
uint32_t NALUnitLength = ; //读取NAL单元长度
memcpy(&NALUnitLength, dataPointer + bufferOffset, AVCCHeaderLength); // 从大端转系统端
NALUnitLength = CFSwapInt32BigToHost(NALUnitLength); NSData* data = [[NSData alloc] initWithBytes:(dataPointer + bufferOffset + AVCCHeaderLength) length:NALUnitLength];
[encoder gotEncodedData:data isKeyFrame:isKeyFrame]; // 移动到写一个块,转成NALU单元
bufferOffset += AVCCHeaderLength + NALUnitLength;
} }
}

    所需要的信息都可以从CMSampleBufferRef中得到。

    

  2、NAL(网络提取层)代码讲解

    直播一中提到了NALU概念上的封装,下面是代码部分:

    代码B:

- (void)gotSpsPps:(NSData*)sps pps:(NSData*)pps
{
// 拼接NALU的header
const char bytes[] = "\x00\x00\x00\x01";
size_t length = (sizeof bytes) - 1;
NSData *ByteHeader = [NSData dataWithBytes:bytes length:length]; // 将NALU的头&NALU的体写入文件
[self.fileHandle writeData:ByteHeader];
[self.fileHandle writeData:sps];
[self.fileHandle writeData:ByteHeader];
[self.fileHandle writeData:pps]; } - (void)gotEncodedData:(NSData*)data isKeyFrame:(BOOL)isKeyFrame
{
NSLog(@"gotEncodedData %d", (int)[data length]);
if (self.fileHandle != NULL)
{
const char bytes[] = "\x00\x00\x00\x01";
size_t length = (sizeof bytes) - 1; //string literals have implicit trailing '\0'
NSData *ByteHeader = [NSData dataWithBytes:bytes length:length];
[self.fileHandle writeData:ByteHeader];
[self.fileHandle writeData:data];
}
}

    结合这张图片:

    一个GOP序列,最前面是sps和pps,它们单独被封装成两个NALU单元,一个NALU单元包含header和具体数据,NALU单元header序列固定为00 00 00 01。那么得到一帧画面时,需要判断该帧是不是I帧,如果是,那么取出sps和pps,再是相关帧的提取写入。(具体参考代码A)。

  3、VTCompressionSession进行硬编码

    a、给出width、height

    b、使用VTCompressionSessionCreate创建compressionSession,并设置使用H264进行编码,相关type是kCMVideoCodecType_H264

    c、b中还需要设置回调函数finishCompressH264Callback,需要在回调函数里面取出编码后的GOP、sps、pps等数据。

    d、设置属性为实时编码,直播必然是实时输出。

    e、设置期望帧数,每秒多少帧,一般都是30帧以上,以免画面卡顿

    f、设置码率(码率: 编码效率, 码率越高,则画面越清晰, 如果码率较低会引起马赛克 --> 码率高有利于还原原始画面,但是也不利于传输)

    g、设置关键帧间隔(也就是GOP间隔)

    h、设置结束,准备编码

    代码:

- (void)setupVideoSession
{
//用于记录当前是第几帧数据
self.frameID = ; //录制视频的宽高
int width = [UIScreen mainScreen].bounds.size.width;
int height = [UIScreen mainScreen].bounds.size.height; // 创建CompressionSession对象,该对象用于对画面进行编码
// kCMVideoCodecType_H264 : 表示使用h.264进行编码
// finishCompressH264Callback : 当一次编码结束会在该函数进行回调,可以在该函数中将数据,写入文件中
//传入的self,就是finishCompressH264Callback回调函数里面的outputCallbackRefCon,通过bridge就可以取出此self
VTCompressionSessionCreate(NULL, width, height, kCMVideoCodecType_H264, NULL, NULL, NULL, finishCompressH264Callback, (__bridge void * _Nullable)(self), &_compressionSession); //设置实时编码,直播必然是实时输出
VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue); //设置期望帧数,每秒多少帧,一般都是30帧以上,以免画面卡顿
int fps = ;
CFNumberRef fpsRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &fps);
VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_ExpectedFrameRate, fpsRef); //设置码率(码率: 编码效率, 码率越高,则画面越清晰, 如果码率较低会引起马赛克 --> 码率高有利于还原原始画面,但是也不利于传输)
int bitRate = * ;
CFNumberRef rateRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bitRate);
VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_AverageBitRate, rateRef);
NSArray *limit = @[@(bitRate * 1.5/), @()];
VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_DataRateLimits, (__bridge CFArrayRef)limit); //设置关键帧间隔(也就是GOP间隔)
//这里设置与上面的fps一致,意味着每间隔30帧开始一个新的GOF序列,也就是每隔间隔1s生成新的GOF序列
//因为上面设置的是,一秒30帧
int frameInterval = ;
CFNumberRef intervalRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &frameInterval);
VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, intervalRef); //设置结束,准备编码
VTCompressionSessionPrepareToEncodeFrames(_compressionSession);
}

    码率:

      初始化后通过VTSessionSetProperty设置对象属性

      编码方式:H.264编码

      帧率:每秒钟多少帧画面

      码率:单位时间内保存的数据量

      关键帧(GOPsize)间隔:多少帧为一个GOP

      参数参考:

    

直播二:iOS中硬编码(VideoToolBox)的更多相关文章

  1. iOS视频硬编码技术

    iOS视频硬编码技术 一.iOS视频采集硬编码 基本原理 硬编码 & 软编码 硬编码:通过系统自带的Camera录制视频,实际上调用的是底层的高清编码硬件模块,即显卡,不使用CPU,速度快 软 ...

  2. IOS中的编码规范

    1.指导原则 [原则1-]首先是为人编写程序,其次才是计算机. 说明:这是软件开发的基本要点,软件的生命周期贯穿产品的开发.测试.生产.用户使用.版本升级和后期维护等长期过程,只有易读.易维护的软件代 ...

  3. ios中base64编码

    参考文章:其中文章的:http://blog.csdn.net/ztp800201/article/details/9470065 下载包 其中 包括GTMBase包下载地址 http://pan.b ...

  4. maven构建项目时硬编码中文乱码问题解决

    场景:1. 项目采用maven作为构建工具.2. 前端页面为jsp,由前端团队独立完成,添加编码配置:<%@ page contentType="text/html;charset=u ...

  5. 01:***VideoToolbox硬编码H.264

    最近接触了一些视频流H264的编解码知识,之前项目使用的是FFMpeg多媒体库,利用CPU做视频的编码和解码,俗称为软编软解.该方法比较通用,但是占用CPU资源,编解码效率不高.一般系统都会提供GPU ...

  6. 使用VideoToolbox硬编码H.264<转>

    文/落影loyinglin(简书作者)原文链接:http://www.jianshu.com/p/37784e363b8a著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. ======= ...

  7. iOS中集成ijkplayer视频直播框架

    ijkplayer 是一款做视频直播的框架, 基于ffmpeg, 支持 Android 和 iOS, 网上也有很多集成说明, 但是个人觉得还是不够详细, 在这里详细的讲一下在 iOS 中如何集成ijk ...

  8. 使用configuration配置结束在quartz.net中使用硬编码Job,Trigger任务提高灵活性

    经常在项目中遇到定时任务的时候,通常第一个想到的是Timer定时器,但是这玩意功能太弱鸡,实际上通常采用的是专业化的第三方调度框架,比如说 Quartz,它具有功能强大和应用的灵活性,我想使用过的人都 ...

  9. 直播软件开发关于Android、iOS中的视频采集步骤

    很多人对直播软件开发还是抱有想法的,但是在这个资本冷静的市场下,直播平台该怎么玩,在直播软件开发过程中哪些功能是必须具备的,这都是值得关注的话题.今天我们给大家分享一份详细的直播软件开发关于Andro ...

随机推荐

  1. 基于Spring、SpringMVC、MyBatis、Druid、Shrio构建web系统

    源码下载地址:https://github.com/shuaijunlan/Autumn-Framework 在线Demo:http://autumn.shuaijunlan.cn 项目介绍 Autu ...

  2. NFS启动时报错Linux NFS:could not open connection for tcp6

    1.1 启动时出现的错误 [root@znix ~]#/etc/init.d/nfs start Shutting down NFS daemon:                          ...

  3. Centos6.8 安装tomcat8.5.11

    1.下载 安装包 wget http://mirrors.aliyun.com/apache/tomcat/tomcat-8/v8.5.11/bin/apache-tomcat-8.5.11.tar. ...

  4. ASP.NET Core的身份认证框架IdentityServer4(9)-使用OpenID Connect添加用户认证

    OpenID Connect OpenID Connect 1.0是OAuth 2.0协议之上的一个简单的身份层. 它允许客户端基于授权服务器执行的身份验证来验证最终用户的身份,以及以可互操作和类似R ...

  5. Hibernate的基础入门(一)

    一Java三层结构 1 web层:struts框架 2 service层:Spring框架 3  dao层 :hibernate框架 相当于MVC的思想 1 M:模型èhibernate框架 2 V: ...

  6. CSS 备忘

    border-radius :  10px  /  40px    10表示X轴半径   40表示Y轴半径   font:italic bold 13px/13px arial,sans-serif; ...

  7. JAVA 后台SSM框架接收安卓端的json数据

    最近项目上与安卓端做JSON数据交互,使用的SSM框架,刚开始的时候感觉很简单,想着不就是把安卓端的JSON数据封装为Bean类对象吗? 于是就这样写了 可是这样一直报400,百度原因是因为请求url ...

  8. linux下socket编程实例

    linux下socket编程实例一.基本socket函数Linux系统是通过提供套接字(socket)来进行网络编程的.网络的socket数据传输是一种特殊的I/O,socket也是一种文件描述符.s ...

  9. Java微信公众平台开发_07_JSSDK图片上传

    一.本节要点 1.获取jsapi_ticket //2.获取getJsapiTicket的接口地址,有效期为7200秒 private static final String GET_JSAPITIC ...

  10. Django中ORM表的创建以及基本增删改查

    Django作为重量级的Python web框架,在做项目时肯定少不了与数据库打交道,编程人员对数据库的语法简单的还行,但过多的数据库语句不是编程人员的重点对象.因此用ORM来操作数据库相当快捷.今天 ...