硬编码相对于软编码来说,使用非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. [Scikit-learn] 2.1 Clustering - Variational Bayesian Gaussian Mixture

    最重要的一点是:Bayesian GMM为什么拟合的更好? PRML 这段文字做了解释: Ref: http://freemind.pluskid.org/machine-learning/decid ...

  2. Numpy数组索引为-1和None

    numpy的数组操作方便,可以用:来切片,用布尔数组或者布尔表达式来查找符合条件的数据,也可以用数组作为另一个数组的索引来查找指定的数据.但有时也会见到数组索引为-1和None.两者的用法如下: 1. ...

  3. panic: interface conversion: interface {} is nil, not chan *sarama.ProducerError

    使用golang kafka sarama 包时,遇到如下问题: 高并发情况下使用同步sync producer,偶尔遇到crash: panic: interface conversion: int ...

  4. windows中通过bat批处理打开exe文件

    1.想要运行的程序: C:\Program Files\Windows Media Player\wmplayer.exe C:\Program Files\Haihaisoft Universal ...

  5. spring框架应用系列三:切面编程(带参数)

    本文系作者原创,转载请注明出处:http://www.cnblogs.com/further-further-further/p/7786715.html 解决问题 1.分离业务监控与业务处理.简单点 ...

  6. NTP时间服务器 搭建

    1.1 NTP简介 NTP(Network Time Protocol,网络时间协议)是用来使网络中的各个计算机时间同步的一种协议.它的用途是把计算机的时钟同步到世界协调时UTC,其精度在局域网内可达 ...

  7. Linux上jdk的安装

    安装jdk    a.检测是否安装了jdk  运行java -version    b.若有需要将其卸载    c.查看安装那些jdk        rpm -qa | grep java    d. ...

  8. JavaScript系列-----Object之toString()和valueOf()方法 (2)

    深入理解toString()和valueOf()函数 1.我们为什么要了解这两种方法 众所周知,toString()函数和valueOf函数,这两个函数是Object类的对象生来就拥有的,而且他们还可 ...

  9. [Bayes] Why we prefer Gaussian Distribution

    最后还是选取一个朴素直接的名字,在此通过手算体会高斯的便捷和神奇. Ref: The Matrix Cookbook 注意,这里的所有变量默认都为多元变量,不是向量就是矩阵.多元高斯密度函数如下: 高 ...

  10. c# word文档与二进制数据的相互转换

    最近项目出使用到了将word文档以二进制的方法存到数据库中,并再次读取出二进制数据转换为word文档.最后总结了一下,不多说看示例方法: 代码 , content.Length);           ...