本文分享来自“关键帧Keyframe”的音视频面试题集锦第 55 期——音视频 iOS 面试题相关内容。
1、全链路“零拷贝”渲染架构设计
题目:在实时滤镜处理中,从 AVCaptureVideoDataOutput 获取数据到 Metal 渲染,再到 AVAssetWriter 编码,如何实现全链路“零拷贝”(Zero-Copy)?请说明关键数据结构及 OC 实现。
深度解析:
- 核心原理: 核心在于利用
CVPixelBuffer共享内存。通过CVMetalTextureCache将CVPixelBuffer直接映射为 Metal 纹理,避免 CPU 与 GPU 之间的数据移动。 - 关键步骤:
- 创建
CVMetalTextureCacheRef。 - 在输出回调中获取
CMSampleBuffer的CVPixelBuffer。 - 通过
CVMetalTextureCacheCreateTextureFromImage映射为 Metal 纹理。 - Metal 渲染后的 Target 指向由
CVPixelBufferPool分配的 Buffer,直接传给编码器。
- 创建
OC 核心代码:
// 1. 创建纹理缓存
CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, mtlDevice, nil, &_textureCache);
// 2. 映射纹理
CVMetalTextureRef textureRef;
CVReturn status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
_textureCache,
pixelBuffer,
nil,
MTLPixelFormatBGRA8Unorm,
width,
height,
0,
&textureRef);
if (status == kCVReturnSuccess) {
id<MTLTexture> mtlTexture = CVMetalTextureGetTexture(textureRef);
// 渲染逻辑...
CFRelease(textureRef); // 必须手动释放映射引用
}
2、VideoToolbox 码率平滑控制与丢帧策略
题目:在弱网推流场景下,如何动态调整 VideoToolbox 编码器的码率?当发送缓冲区堆积严重时,如何设计一个基于 GOP 的丢帧方案?
深度解析:
- 码率调整:
VTCompressionSession支持在运行期修改属性。 - 丢帧策略: 必须基于优先级丢弃。优先级顺序为:
B 帧 > P 帧 > I 帧。若丢弃了某个 P 帧,则该 GOP 内后续所有 P 帧必须全部丢弃,直到下一个 I 帧(IDR)。 - 关键属性:
kVTCompressionPropertyKey_AverageBitRate和kVTCompressionPropertyKey_DataRateLimits。
OC 核心代码:
// 动态修改码率示例
int newBitRate = 800 * 1000; // 800kbps
VTSessionSetProperty(_compressionSession,
kVTCompressionPropertyKey_AverageBitRate,
(__bridge CFTypeRef)@(newBitRate));
// 设置数据率限制,防止瞬时峰值过大
NSArray *limits = @[@(newBitRate / 8), @1]; // 字节数/秒
VTSessionSetProperty(_compressionSession,
kVTCompressionPropertyKey_DataRateLimits,
(__bridge CFArrayRef)limits);
3、音视频同步(AVSync)中的延迟补偿机制
题目:当使用蓝牙耳机播放音视频时,由于蓝牙协议导致的 outputLatency 较大(可能达 200ms+),如何保证音画同步?请说明在自定义播放器中的同步逻辑。
深度解析:
- 参考钟选择: 通常以音频 PTS 为基准。
- 延迟计算: 必须实时从
AVAudioSession获取outputLatency。 - 公式:
实际渲染时间 = 当前视频帧PTS + 音频输出延迟。即视频渲染需要主动“等待”音频在蓝牙链路中的传输消耗。
OC 核心代码:
// 获取当前音频输出的硬件延迟
NSTimeInterval latency = [AVAudioSession sharedInstance].outputLatency;
// 在视频渲染循环中计算
double currentAudioTime = [self getCurrentAudioPresentationTime];
double videoFramePTS = frame.pts;
if (videoFramePTS <= currentAudioTime + latency) {
[self renderVideoFrame:frame]; // 渲染
} else {
// 还没到时间,继续等待或进入下一轮 Loop
}
4、CVPixelBufferPool 的高性能复用与内存管理
题目:在高帧率特效编辑中,如何避免频繁创建 CVPixelBuffer 导致的内存抖动(Churn)?如何处理异步编码过程中 Buffer 的生命周期?
深度解析:
- 复用机制: 使用
CVPixelBufferPool预分配内存,通过kCVPixelBufferPoolMinimumBufferCountKey维持池容量。 - 引用计数:
CVPixelBuffer是基于 C 语言的对象。在异步分发(如推入编码队列)时,必须使用CFRetain,处理完后再CFRelease,否则会导致野指针或内存泄漏。
OC 核心代码:
// 配置 PixelBufferPool
NSDictionary *poolOpts = @{(id)kCVPixelBufferPoolMinimumBufferCountKey: @(15)}; // 预留15个缓存
CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, (__bridge CFDictionaryRef)pixelAttrs, (__bridge CFDictionaryRef)poolOpts, &_bufferPool);
// 从池中获取 Buffer
CVPixelBufferRef newBuffer = NULL;
CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, _bufferPool, &newBuffer);
// 异步处理保护
dispatch_async(_encodeQueue, ^{
CFRetain(newBuffer);
[self encodeBuffer:newBuffer];
CFRelease(newBuffer);
});
5、H.264/H.265 码流解析与极速首帧优化
题目:在对接 RTSP/RTMP 流时,如何手动解析 NALU 单元提取 SPS/PPS 以初始化 VTDecompressionSession?为了首帧“秒开”,在解码端可以做哪些优化?
深度解析:
- 数据格式转换: 网络流通常是 Annex B 格式(00 00 00 01 分隔符),VideoToolbox 需要 AVCC 格式(4 字节长度前缀)。
- 秒开优化:
- GOP 探测: 丢弃第一个 I 帧(IDR)之前的所有数据。
- 异步变同步: 针对首帧,在
decodeFrame时不使用回调,而是尝试立即获取解码后的像素(通过信号量或锁)。 - 解析 Extradata: 提前构造
CMVideoFormatDescription。
OC 核心代码:
// 解析 NALU 类型
uint8_t *data = ...;
int nalu_type = data[4] & 0x1F; // H.264 格式
if (nalu_type == 7) { // SPS
_spsSize = naluSize - 4;
_sps = malloc(_spsSize);
memcpy(_sps, data + 4, _spsSize);
}
// 创建 FormatDescription
const uint8_t* parameterSetPointers[] = { _sps, _pps };
const size_t parameterSetSizes[] = { _spsSize, _ppsSize };
CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
2,
parameterSetPointers,
parameterSetSizes,
4,
&_formatDesc);
学习和提升音视频开发技术,欢迎你加入我们的知识星球

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。