硬编码相对于软编码来说,使用非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. .4-Vue源码之数据劫持(2)

    开播了开播了! vue通过数据劫持来达到监听和操作DOM更新,上一节简述了数组变化是如何监听的,这一节先讲讲对象属性是如何劫持的. // Line-855 Observer.prototype.wal ...

  2. x86-64栈帧中的“红色区域” red zone of stack frame on x86-64

    前几天看System V AMD64 ABI标准的时候发现栈帧的顶部后面有一块"red zone",在学cs:app3e/深入理解操作系统的时候并没有遇到这个,总结一下. 引用标准 ...

  3. A - 棋盘问题 POJ - 1321

    在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别.要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C. ...

  4. 0_Simple__simpleAssert + 0_Simple__simpleAssert_nvrtc

    在核函数中使用强制终止函数 assert().并且在静态代码和运行时编译两种条件下使用. ▶ 源代码:静态使用 #include <windows.h> #include <stdi ...

  5. 动态代理:JDK动态代理和CGLIB代理的区别

    代理模式:代理类和被代理类实现共同的接口(或继承),代理类中存有指向被代理类的索引,实际执行时通过调用代理类的方法.实际执行的是被代理类的方法. 而AOP,是通过动态代理实现的. 一.简单来说: JD ...

  6. maven项目导出依赖的Jar包以及项目本身以jar包形式导出详细教程

    一.maven项目已jar包形式导出 1.首先右键项目,选择Export 2.选择好项目,设置导出路径和jar名字即可: 二.导出maven项目所依赖的所有jar包 1.右键项目,选择Export 2 ...

  7. Python - SIP参考指南 - 介绍

    介绍 本文是SIP4.18的参考指南.SIP是一种Python工具,用于自动生成Python与C.C++库的绑定.SIP最初是在1998年用PyQt开发的,用于Python与Qt GUI toolki ...

  8. Oracle.ManagedDataAccess.dll 连接Oracle数据库不需要安装客户端

    最开始,连接Oracle 数据是需要安装客户端的,ado.net 后来由于微软未来不再支持 System.Data.OracleClient 这个 Data Provider 的研发,从 .NET 4 ...

  9. JDBC数据库编程

    常识名词:ODBC ,JDBC,JDBC API ,JDBC Driver API  数据准备,续上节:   JDBC编程流程 最基本的JDBC操作 本段内容主要完成JDBC的增删查改操作 packa ...

  10. Python的__main__.py用法

    [背景] 在看flower的时候看到__main__.py文件,不知道具体做什么用? 故先进行测试看看. [测试代码] 测试代码目录结构如下: . `-- test |-- __init__.py | ...