音视频 Android 面试题 | 音视频面试题集锦 51 期

来自”关键帧Keyframe”整理的音视频面试题集锦第 51 期之音视频 Android 面试题。

1、【渲染架构篇】SurfaceView 和 TextureView 在底层实现原理上有何本质区别?在播放 DRM(版权保护)视频或追求极致性能(如 4K 60fps)时,应该选择哪一个?为什么?

考察点: SurfaceFlinger、合成机制、缓冲队列、DRM 限制。

参考答案:

核心区别:

  1. SurfaceView (独立窗口):
    • 它拥有自己独立的 Surface,不与 App 的主 UI 窗口共享同一个 Surface
    • 它的渲染数据直接由系统服务 SurfaceFlinger 进行合成 (Compositing),通过 Hardware Composer (HWC) 叠加在 Window 后面。
    • 它就像在 View 树上挖了一个“洞”,独立显示。这意味着它不能像普通 View 一样进行平移、缩放、旋转或设置透明度(虽然 Android 7.0+ 做了改进,但性能代价依然大)。
  2. TextureView (普通 View):
    • 它没有独立的 Window,它也是基于 SurfaceTexture 实现的。
    • 它将生产者产生的图像流转换为 **OpenGL 纹理 (GLES Texture)**,然后由 App 的 UI 渲染线程(Main Thread 或 RenderThread)绘制到 App 的主 Surface 上。
    • 它可以像普通 View 一样做动画和变换。

选择建议:

  • 极致性能 / 4K 视频:选择 SurfaceView
    • 因为 TextureView 需要经过“纹理转换 + App UI 线程绘制 + SurfaceFlinger 合成”这多重步骤,尤其是 4K 视频会占用大量 GPU 带宽进行纹理拷贝和混合,导致发热和耗电。
    • SurfaceView 的“Zero-Copy”特性(数据直接送显)在功耗和帧率上占绝对优势。
  • DRM (版权保护) 视频:必须选择 SurfaceView (或 SurfaceControl)。
    • DRM 内容通常在 TEE (Trusted Execution Environment) 安全环境中解码,产生的受保护内存(Secure Buffer)禁止被 GPU 作为普通纹理读取(防止截屏/录屏)。TextureView 需要将内容转为纹理,这在 DRM 机制下通常是被禁止的或会导致黑屏。

2、【硬编解码篇】在使用 MediaCodec 进行异步解码时,如何实现“零拷贝” (Zero-Copy) 渲染?如果在解码回调中直接操作 ByteBuffer 会有什么性能问题?

考察点: MediaCodec 状态机、Surface 绑定、Native 内存管理。

参考答案:

直接操作 ByteBuffer 的问题:如果使用 MediaCodec.getOutputBuffer() 获取解码后的 YUV 数据(ByteBuffer),然后通过 glPixelStorei 或 glTexImage2D 上传到 OpenGL 纹理:

  1. CPU 拷贝: 数据从内核态驱动拷贝到用户态 ByteBuffer
  2. 总线带宽压力: CPU 再将数据上传到 GPU 内存。对于高分辨率视频,这会造成巨大的带宽瓶颈和 CPU 抖动。

实现 Zero-Copy 的流程:

  1. 配置 Surface: 在 MediaCodec.configure() 时,直接传入一个 Surface 对象(通常来自 SurfaceView 或 SurfaceTexture)。mediaCodec.configure(format, surface, null, 0);
  2. 释放并渲染:在 onOutputBufferAvailable 回调中,拿到 bufferIndex 后,不要去获取 ByteBuffer。而是直接调用:// render = true 表示将该帧数据直接推送到绑定的 Surface
    mediaCodec.releaseOutputBuffer(bufferIndex, true);
  3. 底层机制:此时,解码器(DSP/GPU)解码后的图形缓冲区(GraphicBuffer)会通过 Binder 句柄直接传递给 Surface 的消费者队列。如果绑定的是 SurfaceTexture,则可以通过 updateTexImage() 将该 GraphicBuffer 映射为 OES 外部纹理 (GL_TEXTURE_EXTERNAL_OES),全程没有 CPU 参与数据拷贝,仅有句柄传递。

3、【音频底层篇】Android 的音频延迟(Latency)一直是痛点。请解释什么是 “Fast Path” (低延迟通路)?在使用 AudioTrack 或 OpenSL ES/AAudio 时,如何确保应用能进入 Fast Path?

考察点: AudioFlinger、Mixer 线程、重采样、缓冲区大小匹配。

参考答案:

什么是 Fast Path:Android 的音频系统(AudioFlinger)通常有一个 MixerThread(普通混合线程)和一个 FastMixer(快速混合线程)。

  • 普通路径: 音频数据经过重采样、特效处理、混合,会有较大的缓冲区和延迟(通常 > 50ms)。
  • Fast Path: 绕过普通 Mixer 的大部分处理逻辑,直接将应用层的 Buffer 传递给 ALSA 驱动层,延迟可低至 10ms 甚至更低(Pixel 设备)。

如何确保进入 Fast Path (主要条件):

  1. 采样率匹配 (Sample Rate):必须使用设备原生的采样率。通过 AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE 获取(通常是 48kHz 或 44.1kHz)。如果 App 播放 44.1k 但设备原生是 48k,系统必须进行重采样(Resample),这会直接导致无缘 Fast Path。
  2. 缓冲区大小匹配 (Buffer Size):必须使用设备原生的 Burst Size(突发缓冲区大小)。通过 AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER 获取。
    • 在使用 AudioTrack 时,bufferSizeInBytes 应该是这个值的整数倍。
    • 在使用 AAudio / Oboe 时,通常设置为 PerformanceMode.LowLatency 库会自动处理。
  3. 不使用特效: 这里的流不能挂载任何软件音效(如均衡器、混响)。

Oboe/AAudio 的优势:Google 推出的 AAudio (Android 8.1+) 引入了 MMAP (Memory Mapped) 模式。如果硬件支持,应用层的数据缓冲区直接映射到 ALSA 驱动的内存区域,连 FastMixer 线程的数据拷贝都省了,是目前 Android 音频的最低延迟方案。

4、【视频编辑/处理篇】在开发视频编辑 SDK 时(例如给视频加滤镜并保存),如何利用 OpenGL ES 实现多线程共享上下文 (Context Sharing)?遇到 “EGL_BAD_ACCESS” 错误通常是什么原因?考察点: EGL 状态机、多线程渲染、GL 上下文管理。

参考答案:

场景与实现:视频编辑通常涉及两个线程:

  1. 解码/播放线程: 负责将视频帧解码到纹理。
  2. 编码/保存线程: 负责将处理后的纹理读取并编码写入文件。 为了避免将纹理读回 CPU 再传给另一个线程(极慢),需要使用 EGL Context Sharing

实现步骤:

  1. 创建主线程的 EGLContext (Context A)。
  2. 创建编码线程的 EGLContext (Context B) 时,将 Context A 作为 share_context 参数传入:// EGL14.eglCreateContext(display, config, share_context, attrib_list, offset)
    EGLContext contextB = EGL14.eglCreateContext(display, config, contextA, attrib_list, 0);
  3. 这样,两个线程可以共享纹理 ID (Texture ID) 和 缓冲对象 (VBO/FBO)。

EGL_BAD_ACCESS 原因:即使开启了共享,同一个 EGLContext 在同一时刻只能被一个线程 makeCurrent

  • 如果在 Thread B 尝试操作一个纹理,而此时 Thread A 正在绑定同一个纹理进行绘制,且没处理好同步,可能会报错。
  • 更常见的是:试图在 Thread B 中使用 Thread A 的 Context A 进行 makeCurrent,而 Thread A 还没有解除绑定 (eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT))。
  • 解决方案: 各个线程只 bind 自己的 Context,只共享纹理数据(Data),不共享状态(State)。使用 EGLSync 或 CountDownLatch 确保纹理生成完毕后再在另一个线程使用。

5、【机型适配篇】Android 机型众多,硬编码 (MediaCodec) 出来的 H.264 视频在某些机型上出现颜色错乱(如绿边、颜色对调)或花屏,通常是怎么回事?如何解决?

考察点: YUV 颜色格式碎片化、Stride (步长) 对齐、Profile/Level 兼容性。

参考答案:

原因分析:

  1. Color Format 不一致:MediaCodec 支持多种 YUV 输入格式,不同厂商的编码器支持的格式不同。
    • 有的只支持 COLOR_FormatYUV420Planar (I420)。
    • 有的只支持 COLOR_FormatYUV420SemiPlanar (NV12/NV21)。
    • Qualcomm 芯片常偏好 NV12,MTK 可能偏好 I420。如果输入数据的格式和编码器配置的格式不匹配,就会导致颜色错误(红蓝颠倒)或绿屏。
  2. Stride (步长) 与 Alignment (对齐) 问题:
    • 某些硬件编码器要求 YUV 数据的每一行必须是 16 字节或 32 字节对齐。
    • 例如,视频宽 1080,但编码器要求 Stride = 1088。如果直接塞入 1080 宽的数据,每一行都会错位几个像素,导致画面倾斜或出现绿边。

解决方案:

  1. 查询支持的格式: 在 configure 之前,遍历 MediaCodecInfo.getCapabilitiesForType(...).colorFormats,选择一种当前机器支持的格式(优先选 NV12 或 I420)。
  2. 使用 libyuv 进行转换: 在 Native 层引入 libyuv 库,根据查询到的 Encoder 格式,高效地将 Camera 采集的格式(通常是 NV21)转换为 Encoder 需要的格式。
  3. Surface 输入(推荐):
    • 完全避开手动填充 YUV 数据的深坑。
    • 使用 MediaCodec.createInputSurface() 创建一个 Surface,然后通过 OpenGL 将图像渲染到这个 Surface 上。
    • 优势: 硬件会自动处理颜色格式转换和 Stride 对齐问题,兼容性最好。

学习和提升音视频开发技术,推荐你加入我们的知识星球:【关键帧的音视频开发圈】

音视频 Android 面试题 | 音视频面试题集锦 51 期

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

(0)

相关推荐

发表回复

登录后才能评论