音视频 iOS 面试题 | 音视频面试题集锦 55 期

本文分享来自“关键帧Keyframe”的音视频面试题集锦第 55 期——音视频 iOS 面试题相关内容。

1、全链路“零拷贝”渲染架构设计

题目:在实时滤镜处理中,从 AVCaptureVideoDataOutput 获取数据到 Metal 渲染,再到 AVAssetWriter 编码,如何实现全链路“零拷贝”(Zero-Copy)?请说明关键数据结构及 OC 实现。

深度解析:

  • 核心原理: 核心在于利用 CVPixelBuffer 共享内存。通过 CVMetalTextureCache 将 CVPixelBuffer 直接映射为 Metal 纹理,避免 CPU 与 GPU 之间的数据移动。
  • 关键步骤:
    1. 创建 CVMetalTextureCacheRef
    2. 在输出回调中获取 CMSampleBuffer 的 CVPixelBuffer
    3. 通过 CVMetalTextureCacheCreateTextureFromImage 映射为 Metal 纹理。
    4. 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 字节长度前缀)。
  • 秒开优化:
    1. GOP 探测: 丢弃第一个 I 帧(IDR)之前的所有数据。
    2. 异步变同步: 针对首帧,在 decodeFrame 时不使用回调,而是尝试立即获取解码后的像素(通过信号量或锁)。
    3. 解析 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);

学习和提升音视频开发技术,欢迎你加入我们的知识星球

音视频 iOS 面试题 | 音视频面试题集锦 55 期

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

(0)

相关推荐