下面是第 23 期面试题精选:
- 1、VideoToolbox 遵循哪种视频码率控制策略?如何设置?
- 2、Annex B 如何转换为 AVCC?
- 3、iOS 中如何判断一个视频帧是不是关键帧?
- 4、纹理有哪些环绕方式(wrapping)?
1、VideoToolbox 遵循哪种视频码率控制策略?如何设置?
码率控制策略主要分为以下几种:
- CBR(Constant Bit Rate)恒定码率:一定时间范围内比特率基本保持的恒定。
- 有运动发生时,由于码率恒定,只能通过增大 QP 来减少码字大小,图像质量变差;当场景静止时,图像质量又变好,因此图像质量不稳定。
- 优点是码率处于一个稳定值,缺点是质量不稳定,在复杂运动场景下的视频会很糊。
- 适合在流式播放中应用。
- VBR(Variable Bit Rate)可变码率:码率分配根据图像内容的复杂度进行。
- 简单场景分配较低码率,复杂场景分配较高码率。
- 优点是视频质量稳定,缺点是码率不可控,编码速度较慢。
- 适合的应用场景是本地存储(如视频录制),不适合网络传输(如直播推流)。
- ABR(Average Bitrate)平均目标码率:控制一段时间内的编码平均码率。
- 是在 CBR 和 VBR 两者之间的一种权衡,即设定一段时间的平均码率,在此时间内,对简单的、静态的图像分配低于平均码率的码率,对于复杂的,大量运动的图像分配高于平均码率的码流。
- 速度快,同时兼顾了视频质量和带宽,对于转码速度有要求的情况下也可以选择该模式。
- 适合网络传输。
目前 VideoToolbox 没有属性可以直接设置码率控制策略给调用方,只有开放了 kVTCompressionPropertyKey_DataRateLimits
(为最高码率上限)和 kVTCompressionPropertyKey_AverageBitRate
(编码平均码率)。可以通过 API 属性名称和注释结合编码后的视频码率猜测 VideoToolBox 目前使用的应该是 ABR 视频编码策略。
2、 Annex B 如何转换为 AVCC?
Annex B 格式通常以 0x000001
或 0x00000001
用于标识 NAL 单元的开始。SPS 和 PPS 按流的方式写在头部。
AVCC 格式使用 NALU 长度(固定字节,通常为 4 字节)分隔 NAL;在头部包含 extradata 或 sequence header 的结构体。
以下是 AnnexB 转换为 AVCC 的思路:
- 1、解析 Annex B 格式:读取字节流,识别每个 NAL 单元的起始码,确定每个 NAL 单元的开始和结束位置。
- 2、去除起始码:去除每个 NAL 单元的起始码。
- 3、计算长度:对于每个 NAL 单元,计算其长度(以字节为单位)。
- 4、写入长度前缀:将每个 NAL 单元的长度作为字节序列写入到 AVCC 格式的流中,可能 1 个字节,2 字节或者 4 字节(较为常见),NAL 单元长度会存储在 AVCC 的 extradata 中。
- 5、根据 Annex B 的 SPS 和 PPS 生成对应的 extradata。
- 6、写入 NAL 单元数据:在长度字段后面写入去除起始码后的 NAL 单元数据。
3、iOS 中如何判断一个视频帧是不是关键帧?
在 VideoToolbox 中,可以通过检查给定的 CMSampleBuffer
是否是视频帧,并且是否是关键帧。通过检查 kCMSampleAttachmentKey_NotSync
键的值,如果它为 false
,则说明这是一个关键帧。以下是示例代码
#import <VideoToolbox/VideoToolbox.h>
BOOL isKeyFrame(CMSampleBufferRef sampleBuffer) {
CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
if (!formatDescription) {
return NO;
}
CMMediaType mediaType = CMFormatDescriptionGetMediaType(formatDescription);
if (mediaType != kCMMediaType_Video) {
return NO;
}
CFArrayRef sampleAttachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true);
CFDictionaryRef sampleAttachments = (CFDictionaryRef)CFArrayGetValueAtIndex(sampleAttachmentsArray, 0);
BOOL isKeyFrame = !CFDictionaryContainsKey(sampleAttachments, kCMSampleAttachmentKey_NotSync);
return isKeyFrame;
}
4、纹理有哪些环绕方式(wrapping)?
重复(GL_Repeat)
:纹理在每个纹理坐标轴上重复出现,当纹理坐标超出[0,1]
范围时,纹理会在该轴上重复出现。这种方式适用于创建无缝平铺效果。这是对纹理的默认行为。镜像重复(GL_MIRRORED_REPEAT)
:与重复(GL_Repeat)方式相似,但当纹理坐标超出[0,1]
范围时,会将其镜像翻转后再重复出现。这可以有效减少纹理重复造成的视觉疲劳。夹取到边缘(GL_CLAMP_TO_EDGE)
:与夹取方式类似,但在超出范围时,会使用边缘纹素的颜色,产生一种边缘被拉伸的效果。夹取到边框(GL_CLAMP_TO_BORDER)
:超出范围时,使用指定的边框颜色。这种方式通常用于在超出纹理范围时填充边框颜色,避免黑边。
纹理环绕方式