编码❝
这一阶段,常用到的一个三方库是 GPUImage,这个库提供了常见的 100 滤镜的算法。它有三个版本:
GPUImage 1:OC OpenGL
GPUImage 2:Swift OpenGL
GPUImage 3:Swift Metal
在拿到采集处理后的音视频原数据之后,还要经过编码压缩才能往外传输数据。
压缩分为两种,有损和无损,区别如下:
- 有损压缩:解压缩后的数据和压缩前的不一致,压缩过程中会丢失一些人眼人耳不敏感的图像或音频信息,丢失的信息不可恢复。
- 无损压缩:压缩前和压缩后的数据一致,优化数据的排列等。
视频的编码,是为了压缩它的大小,以便于能够更快的在网络上传输。很明显,这是一个有损压缩过程。在这个过程中,会丢弃掉一些冗余信息,常见的冗余信息如下:
- 空间冗余:图像相邻像素之间有较强的相关性
- 时间冗余:视频序列的相邻图像之间内容相似
- 视觉冗余:人的视觉系统对某些细节不敏感
- 知识冗余:规律性的结构可由先验知识和背景知识得到
- 结构冗余:某些图片中固定存在的分布模式
总结来说:编码就是一个丢弃冗余信息的压缩过程。
视频编码过程具体的编码过程如下:
- 找到冗余信息:每一帧原始采样分块
- 把图片分组:有差别的像素只有 10% 以内的点,亮度差值变化不超过 2%,而色度差值的变化只有 1% 以内一组称为 GOP (包括一个 I 帧,多个 P/B 帧)
- 逐帧进行编码
这个是剪映的一个截图,我在里面放了一个30帧的视频。
先看左下角红框里,我框了5帧图片出来,这几帧图片,内容差别很小,我们可以把他们分成一个组。来处理我们上面说过的时间冗余信息。每一组图片叫做 GOP 。
再看右边这个小箭头,我把箭头尾部,肩膀这部分放大了,可以看到一个个像素,每个小红框里假如说是有16*16个像素,就是一个分块。在这个分块,我们处理上面说过的空间冗余。
分组,分块之后。一帧帧的去处理图片。这就是编码的大概流程。
I P B 帧帧的编码方式:
- 帧内压缩:压缩一帧图像时,仅考虑本帧的数据而不考虑相邻帧之间的冗余信息。
- 帧间压缩:相邻几帧的数据有很大相关性,连续的视频相邻帧之间有冗余信息,又称作时间压缩。
在对视频帧编码后,原始视频数据会被压缩成三种不同类型的视频帧:I帧、P帧、B帧
- 如下图所示,第一张图片是 I 帧,第二个是 P 帧。P 帧相对于 I 帧,三个豆豆往左移动了一点,那么在编码 P 帧的时候,可以只记录这个偏移量,其他的信息就参考 I 帧来就行。
- 第三个 B 帧,他的豆豆数量和第四个 I 帧一样。那我可以直接记录前三个豆豆相对于P帧的位移,以及最后一个豆豆相对于后面I帧的位移,就可以编解码这一帧数据了。
- I 帧:关键帧,完整编码的帧。可以理解成一张完整画面,不依赖其他帧。
- P帧:预测帧,参考前面的 I 帧或 P 帧进行编解码。
- B帧:双向帧,参考前面的 I 帧或 P 帧,和后面的 P 帧进行编解码。
H.264 的压缩方式,是在两方面对视频帧进行了压缩:
- 空间:压缩独立视频帧(帧内压缩)
- 时间:通过以组(GOP)为单位的视频帧压缩冗余数据(帧间压缩)
H.265 是基于 H.264 基础上,做了些改进,本质上是一样的。
核心方法如下:
// 创建编码器
OSStatus status = VTCompressionSessionCreate(NULL, _configuration.videoSize.width, _configuration.videoSize.height, kCMVideoCodecType_H264, NULL, NULL, NULL, VideoCompressonOutputCallback, (__bridge void *)self, &compressionSession);
// 配置编码器属性
VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFTypeRef)@(_videoMaxKeyframeInterval));
//...
// 编码前资源配置
VTCompressionSessionPrepareToEncodeFrames(compressionSession);
// 编码
OSStatus status = VTCompressionSessionEncodeFrame(compressionSession, pixelBuffer, presentationTimeStamp, duration, (__bridge CFDictionaryRef)properties, (__bridge_retained void *)timeNumber, &flags);
音频编码
数字音频压缩编码是在保证信号在听觉方面不产生失真的前提下,对音频数据信号进行尽可能的压缩。 去除声音中冗余成分(不能被人耳察觉的信号,他们对声音的音色、音调等信息没有任何帮助)。
音频冗余信息如下:
- 人耳能听到的频率范围是 20Hz ~ 20kHz,超过这个范围的声音都可以丢弃。
- 当一个强音信号和弱音信号同时存在时,弱音信号将被强音信号所掩蔽而听不见,这样弱音信号就可以视为冗余信息不用传送
音频编码核心方法如下:
#import <AudioToolbox/AudioToolbox.h>
// 创建编码器
OSStatus result = AudioConverterNewSpecific(&inputFormat, &outputFormat, 2, requestedCodecs, &m_converter);;
// 编码
AudioConverterFillComplexBuffer(m_converter, inputDataProc, &buffers, &outputDataPacketSize, &outBufferList, NULL)
封装
封装就是把编码后的音视频数据,打包放到一个容器格式里。例如 mp4、flv、mov 等
每一种封装格式有它适合的领域。比方说avi这种格式,它不支持流媒体播放,只能说是有一个完整的打包好的视频文件,那它就是适合在 bt下载领域应用,而不适合直播这种场景了。
直播中比较常用的两种封装格式是 flv 和 ts,他们的区别在于编码器类型不一样。
FLVflv 支持 h.264 & AAC 编码器,我们这里就以他为例,看一下flv的文件结构是怎样的:
首先是有一个 flv header,里面包含 flv 的文件表示,以及flv版本信息等等。然后是flv body。body又分为一个个 tag,在 tag 里面才是具体的音频数据,或者视频数据信息。