直播二:iOS中硬编码(VideoToolBox)
硬编码相对于软编码来说,使用非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:存放编解码前后的视频图像的容器数据结构。
// 编码完成回调
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)的更多相关文章
- iOS视频硬编码技术
iOS视频硬编码技术 一.iOS视频采集硬编码 基本原理 硬编码 & 软编码 硬编码:通过系统自带的Camera录制视频,实际上调用的是底层的高清编码硬件模块,即显卡,不使用CPU,速度快 软 ...
- IOS中的编码规范
1.指导原则 [原则1-]首先是为人编写程序,其次才是计算机. 说明:这是软件开发的基本要点,软件的生命周期贯穿产品的开发.测试.生产.用户使用.版本升级和后期维护等长期过程,只有易读.易维护的软件代 ...
- ios中base64编码
参考文章:其中文章的:http://blog.csdn.net/ztp800201/article/details/9470065 下载包 其中 包括GTMBase包下载地址 http://pan.b ...
- maven构建项目时硬编码中文乱码问题解决
场景:1. 项目采用maven作为构建工具.2. 前端页面为jsp,由前端团队独立完成,添加编码配置:<%@ page contentType="text/html;charset=u ...
- 01:***VideoToolbox硬编码H.264
最近接触了一些视频流H264的编解码知识,之前项目使用的是FFMpeg多媒体库,利用CPU做视频的编码和解码,俗称为软编软解.该方法比较通用,但是占用CPU资源,编解码效率不高.一般系统都会提供GPU ...
- 使用VideoToolbox硬编码H.264<转>
文/落影loyinglin(简书作者)原文链接:http://www.jianshu.com/p/37784e363b8a著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. ======= ...
- iOS中集成ijkplayer视频直播框架
ijkplayer 是一款做视频直播的框架, 基于ffmpeg, 支持 Android 和 iOS, 网上也有很多集成说明, 但是个人觉得还是不够详细, 在这里详细的讲一下在 iOS 中如何集成ijk ...
- 使用configuration配置结束在quartz.net中使用硬编码Job,Trigger任务提高灵活性
经常在项目中遇到定时任务的时候,通常第一个想到的是Timer定时器,但是这玩意功能太弱鸡,实际上通常采用的是专业化的第三方调度框架,比如说 Quartz,它具有功能强大和应用的灵活性,我想使用过的人都 ...
- 直播软件开发关于Android、iOS中的视频采集步骤
很多人对直播软件开发还是抱有想法的,但是在这个资本冷静的市场下,直播平台该怎么玩,在直播软件开发过程中哪些功能是必须具备的,这都是值得关注的话题.今天我们给大家分享一份详细的直播软件开发关于Andro ...
随机推荐
- HDU 6092 Rikka with Subset
Rikka with Subset Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others ...
- NFC (Near Filed Communication)
NFC的用途:近场通信(Near Field Communication,NFC),又称近距离无线通信,是一种短距离的高频无线通信技术,允许电子设备之间进行非接触式点对点数据传输(在十厘米内)交换数据 ...
- 享受Python和PHP动态类型检查语言的快感
前言 写这文章的时候特地查了资料,以确保我没有说错关于Python和PHP的类型机制. 所以这里放一张图,关于强弱类型与动态/静态类型检查的区分 从分类上看,PHP属于弱类型语言,而Python属于强 ...
- C#操作Excel(读取)
一.使用OleDb,这个法子好像不大好使.容易读错.引用System.Data.OleDb; /**//// <summary> /// 返回Excel数据源 ...
- Nodejs学习笔记(十五)--- Node.js + Koa2 构建网站简单示例
目录 前言 搭建项目及其它准备工作 创建数据库 创建Koa2项目 安装项目其它需要包 清除冗余文件并重新规划项目目录 配置文件 规划示例路由,并新建相关文件 实现数据访问和业务逻辑相关方法 编写mys ...
- 深入理解CSS盒模型
如果你在面试的时候面试官让你谈谈对盒模型的理解,你是不是不知从何谈起.这种看似简单的题其实是最不好答的. 下面本文章将会从以下几个方面谈谈盒模型. 基本概念:标准模型 和IE模型 CSS如何设置这两种 ...
- 照虎画猫写自己的Spring
从细节跳出来 看了部分Spring的代码,前面用了四篇内容写了一些读书笔记. 回想起来,论复杂度,Spring够喝上好几壶的.他就像一颗枝繁叶茂的大树,远处看,只是一片绿:走近看,他为你撑起一片小天地 ...
- SpringMVC处理ajax请求的注意事项
.首先要知道ajax请求的核心是JavaScrip对象和XmlHttpRequest,而浏览器请求的核心是浏览器 ajax请求 浏览器请求 场景一:使用ajax获取session中的user 从上图可 ...
- CSS样式命名整理(非原创)
非原创,具体出自哪里忘了,如果侵害您的利益,请联系我. CSS样式命名整理 页面结构 容器: container/wrap 整体宽度:wrapper 页头:header 内容:content 页面主体 ...
- 《java.util.concurrent 包源码阅读》18 Exchanger
Exchanger可以看做双向数据传输的SynchronousQueue,即没有生产者和消费者之分,任意两个线程都可以交换数据. 在JDK5中Exchanger被设计成一个容量为1的容器,存放一个等待 ...