来自”关键帧Keyframe”整理的音视频面试题集锦第 51 期之音视频 Android 面试题。
1、【渲染架构篇】SurfaceView 和 TextureView 在底层实现原理上有何本质区别?在播放 DRM(版权保护)视频或追求极致性能(如 4K 60fps)时,应该选择哪一个?为什么?
考察点: SurfaceFlinger、合成机制、缓冲队列、DRM 限制。
参考答案:
核心区别:
- SurfaceView (独立窗口):
- 它拥有自己独立的
Surface,不与 App 的主 UI 窗口共享同一个Surface。 - 它的渲染数据直接由系统服务 SurfaceFlinger 进行合成 (Compositing),通过 Hardware Composer (HWC) 叠加在 Window 后面。
- 它就像在 View 树上挖了一个“洞”,独立显示。这意味着它不能像普通 View 一样进行平移、缩放、旋转或设置透明度(虽然 Android 7.0+ 做了改进,但性能代价依然大)。
- 它拥有自己独立的
- TextureView (普通 View):
- 它没有独立的 Window,它也是基于
SurfaceTexture实现的。 - 它将生产者产生的图像流转换为 **OpenGL 纹理 (GLES Texture)**,然后由 App 的 UI 渲染线程(Main Thread 或 RenderThread)绘制到 App 的主 Surface 上。
- 它可以像普通 View 一样做动画和变换。
- 它没有独立的 Window,它也是基于
选择建议:
- 极致性能 / 4K 视频:选择
SurfaceView。- 因为
TextureView需要经过“纹理转换 + App UI 线程绘制 + SurfaceFlinger 合成”这多重步骤,尤其是 4K 视频会占用大量 GPU 带宽进行纹理拷贝和混合,导致发热和耗电。 SurfaceView的“Zero-Copy”特性(数据直接送显)在功耗和帧率上占绝对优势。
- 因为
- DRM (版权保护) 视频:必须选择
SurfaceView(或SurfaceControl)。- DRM 内容通常在 TEE (Trusted Execution Environment) 安全环境中解码,产生的受保护内存(Secure Buffer)禁止被 GPU 作为普通纹理读取(防止截屏/录屏)。
TextureView需要将内容转为纹理,这在 DRM 机制下通常是被禁止的或会导致黑屏。
- DRM 内容通常在 TEE (Trusted Execution Environment) 安全环境中解码,产生的受保护内存(Secure Buffer)禁止被 GPU 作为普通纹理读取(防止截屏/录屏)。
2、【硬编解码篇】在使用 MediaCodec 进行异步解码时,如何实现“零拷贝” (Zero-Copy) 渲染?如果在解码回调中直接操作 ByteBuffer 会有什么性能问题?
考察点: MediaCodec 状态机、Surface 绑定、Native 内存管理。
参考答案:
直接操作 ByteBuffer 的问题:如果使用 MediaCodec.getOutputBuffer() 获取解码后的 YUV 数据(ByteBuffer),然后通过 glPixelStorei 或 glTexImage2D 上传到 OpenGL 纹理:
- CPU 拷贝: 数据从内核态驱动拷贝到用户态
ByteBuffer。 - 总线带宽压力: CPU 再将数据上传到 GPU 内存。对于高分辨率视频,这会造成巨大的带宽瓶颈和 CPU 抖动。
实现 Zero-Copy 的流程:
- 配置 Surface: 在
MediaCodec.configure()时,直接传入一个Surface对象(通常来自SurfaceView或SurfaceTexture)。mediaCodec.configure(format, surface, null, 0); - 释放并渲染:在
onOutputBufferAvailable回调中,拿到 bufferIndex 后,不要去获取ByteBuffer。而是直接调用:// render = true 表示将该帧数据直接推送到绑定的 Surface
mediaCodec.releaseOutputBuffer(bufferIndex, true); - 底层机制:此时,解码器(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 (主要条件):
- 采样率匹配 (Sample Rate):必须使用设备原生的采样率。通过
AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE获取(通常是 48kHz 或 44.1kHz)。如果 App 播放 44.1k 但设备原生是 48k,系统必须进行重采样(Resample),这会直接导致无缘 Fast Path。 - 缓冲区大小匹配 (Buffer Size):必须使用设备原生的 Burst Size(突发缓冲区大小)。通过
AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER获取。- 在使用
AudioTrack时,bufferSizeInBytes 应该是这个值的整数倍。 - 在使用
AAudio/Oboe时,通常设置为PerformanceMode.LowLatency库会自动处理。
- 在使用
- 不使用特效: 这里的流不能挂载任何软件音效(如均衡器、混响)。
Oboe/AAudio 的优势:Google 推出的 AAudio (Android 8.1+) 引入了 MMAP (Memory Mapped) 模式。如果硬件支持,应用层的数据缓冲区直接映射到 ALSA 驱动的内存区域,连 FastMixer 线程的数据拷贝都省了,是目前 Android 音频的最低延迟方案。
4、【视频编辑/处理篇】在开发视频编辑 SDK 时(例如给视频加滤镜并保存),如何利用 OpenGL ES 实现多线程共享上下文 (Context Sharing)?遇到 “EGL_BAD_ACCESS” 错误通常是什么原因?考察点: EGL 状态机、多线程渲染、GL 上下文管理。
参考答案:
场景与实现:视频编辑通常涉及两个线程:
- 解码/播放线程: 负责将视频帧解码到纹理。
- 编码/保存线程: 负责将处理后的纹理读取并编码写入文件。 为了避免将纹理读回 CPU 再传给另一个线程(极慢),需要使用 EGL Context Sharing。
实现步骤:
- 创建主线程的 EGLContext (Context A)。
- 创建编码线程的 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); - 这样,两个线程可以共享纹理 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 兼容性。
参考答案:
原因分析:
- Color Format 不一致:
MediaCodec支持多种 YUV 输入格式,不同厂商的编码器支持的格式不同。- 有的只支持
COLOR_FormatYUV420Planar(I420)。 - 有的只支持
COLOR_FormatYUV420SemiPlanar(NV12/NV21)。 - Qualcomm 芯片常偏好 NV12,MTK 可能偏好 I420。如果输入数据的格式和编码器配置的格式不匹配,就会导致颜色错误(红蓝颠倒)或绿屏。
- 有的只支持
- Stride (步长) 与 Alignment (对齐) 问题:
- 某些硬件编码器要求 YUV 数据的每一行必须是 16 字节或 32 字节对齐。
- 例如,视频宽 1080,但编码器要求
Stride = 1088。如果直接塞入 1080 宽的数据,每一行都会错位几个像素,导致画面倾斜或出现绿边。
解决方案:
- 查询支持的格式: 在
configure之前,遍历MediaCodecInfo.getCapabilitiesForType(...).colorFormats,选择一种当前机器支持的格式(优先选 NV12 或 I420)。 - 使用 libyuv 进行转换: 在 Native 层引入
libyuv库,根据查询到的 Encoder 格式,高效地将 Camera 采集的格式(通常是 NV21)转换为 Encoder 需要的格式。 - Surface 输入(推荐):
- 完全避开手动填充 YUV 数据的深坑。
- 使用
MediaCodec.createInputSurface()创建一个 Surface,然后通过 OpenGL 将图像渲染到这个 Surface 上。 - 优势: 硬件会自动处理颜色格式转换和 Stride 对齐问题,兼容性最好。
学习和提升音视频开发技术,推荐你加入我们的知识星球:【关键帧的音视频开发圈】

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