Android音视频开发面试核心考点:从MediaCodec到播放器架构
最近在帮团队做Android音视频开发的面试复盘,筛了三十多份简历、面了十几位候选人,发现一个很普遍的现象:很多人简历上写着“熟悉MediaCodec、了解FFmpeg”,但一追问到I帧P帧的区别、时间戳怎么对齐、硬解黑屏怎么降级,就开始含糊其辞。音视频这块内容又多又杂,网上资料大多是复制粘贴的API文档,真正能把原理讲透、把坑说清的面试题整理反而很少。
这篇文章我打算换个角度来写,不按“基础知识—框架—实战”这种教科书式顺序,而是把面试官真正会追问的考点逐个拆开,从底层概念一路聊到播放器架构和性能优化。无论你是刚准备转音视频方向的学生,还是已经写了几年业务、想往多媒体底层深挖的Android开发,都可以拿这篇做一份对照自查清单。
1. 音视频基础概念,面试官最爱往深里问的知识点
音视频面试第一关往往是基础概念,但这里有个陷阱:大多数人都能说个大概,可一旦面试官开始追问“为什么”和“怎么算”,就露馅了。
1.1 采样、量化、编码:底层的三步曲
任何模拟信号要变成数字信号,都要经过采样、量化、编码三个过程。声音的采样是把连续的声波在时间轴上离散化,图像的采样是在空间轴上离散化,采样率决定了时间/空间的细腻程度;量化则是把连续的幅度值映射到有限个离散等级,位深决定了精度;编码是在数字化的基础上做压缩,去掉冗余信息。
面试时如果只答到这里,大概是及格分。想拿高分,必须把几个关键参数挂上钩:
- 音频采样率44100Hz是CD标准,48000Hz是视频制作常用标准,Android原生播放器对48000Hz的支持更好。
- 位深16bit每样本是常规水平,24bit以上常用于专业录音,位深越大动态范围越好,但数据量也线性增长。
- 视频的采样更像是对每个像素的亮度和色度分别采样,YUV420格式里每4个亮度样本共享一组色度样本,这直接决定了视频编码前的数据量。
一个简单的计算,一秒钟未经压缩的48kHz、16bit、双声道音频,数据量是48,000 × 16 / 8 × 2 = 192,000字节,约187.5KB。而一秒钟1080p30帧的YUV420原始视频,每帧像素为1920×1080×1.5字节,约2.9MB,再加上帧率就是每秒91MB。当你把这两个数字摆出来,面试官就知道你是真的理解数据量概念,而不是背了公式。
1.2 I帧、P帧、B帧和GOP:别只背名字
视频编码里最重要的概念之一就是帧类型。I帧是关键帧,包含完整画面信息,解码时不依赖其他帧;P帧是前向预测帧,只保存和前一帧的差值;B帧是双向预测帧,同时参考前后帧。GOP是两个I帧之间的完整帧序列,GOP越长,压缩率越高,但随机访问能力越差。
面试官问到这里,通常会跟进一个问题:GOP设置多大合适?这个问题没有标准答案,取决于场景。
直播场景要求低延迟,GOP一般设置1到2秒,这样关键帧间隔短,观众拖到任意位置都能快速出画面;点播场景追求压缩率,GOP可以放到4到5秒甚至更长。GOP越长,同码率下画质越好,但遇到丢包时画面花屏的恢复时间也越长。还有一类特殊场景是视频剪辑,如果GOP里有B帧,切割点不在I帧上就会导致解码困难,所以剪辑工具的转码服务通常会强制关闭B帧。
这里有一个我在实际对接播放器时踩过的坑:很多视频网站的CDN切流、广告插入都依赖GOP对齐。如果编码器输出的GOP没有设置为固定大小,观众在观看时一旦发生码流切换,画面大概率会先花掉一两秒。所以在做HLS切片或者直播转码时,一定要确认编码器设置的GOP大小是固定的,而不是“自适应”。
1.3 DTS和PTS:音视频同步的关键
DTS是解码时间戳,表示数据包什么时候应该被解码;PTS是显示时间戳,表示解码后的帧什么时候应该被渲染。在有B帧的场景里,解码顺序和显示顺序是不一致的,所以必须有这两个时间戳来分别指导解码器和渲染器。
举个具体例子:一个GOP的帧顺序是I帧、B帧、P帧、B帧、P帧,解码顺序是I、P、B、P、B,而显示顺序是I、B、P、B、P。如果只用PTS来驱动解码器,解码器根本没法工作,因为B帧的解码依赖后面的P帧。所以在做播放器时,喂给解码器的顺序必须按DTS排好,渲染时再根据PTS对齐。
时间戳的精度也是面试高频点。Android里MediaCodec输出的时间戳单位是微秒,而很多封装格式用的是毫秒。换算过程中最忌讳用整型除法直接截断,否则长时间播放后音画偏移会越积越大。我之前见过一个播放器项目,音频时间戳做了毫秒到微秒的乘法,视频时间戳没做,播到半小时后声音比画面快了近一秒。这种问题靠日志排查很难发现,最后是拉长时间对比两个时间戳曲线才定位到。
1.4 码率、帧率、分辨率,换算关系要心里有数
这三个参数的关系就是:码率 = 每帧平均字节数 × 帧率 × 8。给定码率和帧率,可以反推每帧的数据预算。
1080p30、H.264编码,要想画面干净,码率大概需要4到8Mbps。如果码率给到2Mbps,画面静止时还行,一动起来就会出现块效应和边缘振铃。面试官有时候会故意问:同样分辨率,为什么H.265比H.264省一半码率?本质是H.265引入了更灵活的块划分(CU/PU/TU)和更精细的帧内预测模式,在相同感知画质下能用更少的比特描述像素块。
还有一个容易忽略的点是:帧率并不是越高越好。高帧率意味着单帧的编码预算被压缩,如果码率不变,从30fps提到60fps,单帧画质反而下降。所以做视频录制类App时,帧率和码率需要一起调,不能只动一个参数。
2. Android媒体组件底细:MediaCodec、Extractor、Muxer、Render
基础概念过关后,面试官基本就会把话题拉到Android平台上,考察你对官方多媒体组件的熟悉程度。这一轮是分辨“真用过”和“背过文档”的关键环节。
2.1 MediaCodec状态机,照背会翻车
MediaCodec是Android音视频硬编硬解的核心API,但要真正用好它,必须清楚它的状态机。它大体上有Stopped、Executing、Released三个大状态,其中Executing里又分Flushed、Running、End-of-Stream三个子状态。
面试官最常见的考法是:你向一个刚启动的MediaCodec提交InputBuffer为什么会返回错误?因为你没有等待它进入Running状态。正确的做法是循环查询状态,判断返回值是INFO_TRY_AGAIN_LATER还是BUFFER_FLAG_END_OF_STREAM,再决定是继续投递还是走结束流程。
还有一个大数据开发者不太注意的细节:MediaCodec有两种操作模式,同步模式和异步模式。同步模式用dequeueInputBuffer/dequeueOutputBuffer循环驱动,逻辑清晰但线程管理麻烦;异步模式通过回调通知输入输出,代码更优雅,但对状态机理解要求更高。新项目我更推荐异步模式,回调里用HandlerThread串行处理,能避免大量并发问题。
这里顺便补充一个我在硬解时遇到的真实case:某型号的芯片在解码H.264 High Profile视频时偶发绿屏,log里没有异常,后来发现是MediaCodec创建时没有显式设置KEY_PROFILE和KEY_LEVEL。个别厂商的底层实现不会从码流里自动探测Profile,必须由上层指定。从那以后我在所有硬解初始化里都会带上Profile和Level,兼容性问题少了一大半。
2.2 MediaExtractor与MediaMuxer,封装和解封装不容易
MediaExtractor负责从文件里读取出音视频数据,MediaMuxer负责把编码后的数据写回容器。把这两个组件分开考查,是因为很多做播放器的人只用了MediaExtractor,没写过MediaMuxer,而做录制功能的人恰恰反过来。
我在面试里喜欢问一个综合题:如果要实现一个MP4转TS的工具,需要怎么设计?核心答案是:用MediaExtractor按帧读取出H.264 Annex-B码流,转换成AVCC格式后,再用MediaMuxer写入MP4。反过来,从MP4转到TS,需要重新解析NALU,添加PPS/SPS和SEI。这个过程中,MediaExtractor输出的CSD信息(Codec Specific Data)非常重要,丢失了它,解码器根本无法初始化。
实际操作中,MediaMuxer还有个限制:MP4格式要求写入的sample必须按PTS单调递增。如果你的编码器输出了B帧,编码顺序和显示顺序不一致,直接写入会报错。这就要在Muxer前做一个帧排序。我在某个录制模块里,就因为这个原因踩过坑,最后用优先级队列按PTS排好序才解决。
封装格式的差异也是面试官爱问的点,比如MP4和FLV的区别、TS切片时长怎么选。MP4适合点播,因为moov box可以在文件头,方便拖拽;FLV适合直播,因为结构简单、支持流式写入;TS适合HLS切片,因为天生支持多路音视频复用。这些封装层面的知识,在工作里真正碰到的时候才知道有多重要。
2.3 SurfaceView、TextureView和GLSurfaceView,选型不是拍脑袋
做播放器或相机预览,一定会遇到View选型问题。面试官考查的是你对渲染管线的理解,而不是View本身。
SurfaceView的特点是独立窗口,它有独立的Surface可以跑到应用视图层级之后,用独立的线程去做绘制。它的优点是效率高、内存占用低,因为绘制不走应用GPU合成;缺点是它不能做动画变形,也没法被其他View遮挡,最麻烦的是它不能在其他表面做分层动画。
TextureView是把Surface包装在View体系内部,需要走View的绘制流程,所以能支持旋转、缩放、平移等动画。但它多了一次合成,性能开销比SurfaceView大。Android 7.0之前TextureView还有一个致命问题,就是无法在SurfaceView和TextureView之间无缝切换,切换时会出现黑屏闪烁。
GLSurfaceView是专门为OpenGL ES渲染设计的SurfaceView,适合做滤镜、特效渲染。但它的生命周期和GL上下文绑定,做复杂渲染时要注意GL线程和UI线程的同步。如果只是做一个简单播放器,我的建议是优先SurfaceView;如果要做贴纸、旋转、动画播放器,选TextureView;如果要做实时滤镜,直接上GLSurfaceView。还有一种场景是录制屏幕或者沉浸式播放,用TextureView配合MediaCodec的Surface输入模式更容易实现。
2.4 AudioTrack与OpenSL ES,声音输出不止一种方式
面试时很多人谈音频只知道MediaPlayer,但真正做底层音频播放一定会接触到AudioTrack和OpenSL ES。
AudioTrack是Android上层API,可以把PCM数据直接写到音频设备。用AudioTrack播放音频,需要指定音频流类型(STREAM_MUSIC、STREAM_VOICE_CALL等)、采样率和声道格式。这里有个关键点,AudioTrack也有一个缓冲区大小问题,设置过小会导致播放卡顿,设置太大会增加延迟。一般建议用AudioTrack.getMinBufferSize()拿到系统建议的最小缓冲长度,再根据业务需要乘以2到3倍。
OpenSL ES是C层API,性能比AudioTrack好,因为绕过了Java层的部分开销。很多跨平台播放器内核都使用OpenSL ES实现音频输出。面试如果聊到自研播放器,这个话题是加分项,但要说得清楚:OpenSL ES的播放回调是直接从音频引擎的线程触发的,在回调里不能做任何耗时操作,否则会直接影响声音连续性。纹理上传、写日志、加锁这类操作都应该挪出去。
3. 播放器架构与性能优化,从能播到播得好
播放器不只是MediaCodec加MediaPlayer,一个完整的架构设计题可以考查从解协议、解封装、解码到渲染的整条链路。面试官在这块通常会问得很开放,关键看你脑子里有没有一张完整的地图。
3.1 播放器整体架构,画出模块图
一个标准的播放器包含:数据读取模块(DataSource)、解封装模块(Demuxer)、解码模块(Decoder)、渲染模块(Renderer)和同步控制模块(Sync)。
我在回答这类问题时会直接在白板上画出模块边界和数据流:DataSource从本地文件或网络流读取字节,交给Demuxer按封装格式拆帧,得到编码后的音频包和视频包,再分别送入音频解码器和视频解码器,解码后的数据分别送到AudioTrack和Surface渲染,同步模块通过PTS和系统时钟来对齐两条渲染链路。
面试官紧接着就会问:整个播放流程中哪些模块可以复用?这就涉及到播放器分层设计了。我建议把数据源、解封装、解码收敛到播放内核层,渲染和事件回调放在外层。内核层用C/C++编写的好处是可以跨平台复用,但缺点是和Java层的交互成本高,要在JNI层做严格的数据拷贝控制。
跨平台播放器这块是近几年的大热门,像ExoPlayer本身就是Android平台内嵌的多媒体框架,而IjkPlayer是FFmpeg的Android封装。两者对比下来,ExoPlayer的优势是官方维护、协议支持完善、MediaCodec硬解适配好;IjkPlayer的优势是媒体格式兼容性更强,但项目维护趋于停滞。面试官问你这个对比,更多是想了解你对社区生态的判断力。
一个偏开放的追问:自研播放器时,你会用什么语言写核心?我的答案始终是C/C++加JNI,核心编解码、网络协议、解封装全部下沉到C层,Java只做生命周期管理和UI渲染。理由很简单:移植性好、性能可控、调试方便。但这里要提醒一下,要处理好Native崩溃的堆栈符号化,否则线上问题完全无从下手。
3.2 音视频同步的三种方案,必须会推导
音视频同步的经典解法是主时钟同步。选一种时间源作为主时钟,其他媒体向它对齐。
第一种方案以音频为主时钟,这是最常见也最合理的选择。因为人耳对声音断断续续比画面跳帧更敏感,音频播放的连续性要求极高。具体操作:音频正常连续播放,视频帧根据PTS与音频时钟的差值来决定是等待还是丢帧。如果视频帧比音频慢,就延迟渲染;如果视频帧比音频快太多,就丢弃旧帧追赶上。
第二种方案以视频为主时钟,用于无声视频或音频数据严重缺失的场景,此时画面连续性优先,音频播放速度跟随视频节奏。
第三种方案是外部时钟,用系统单调时钟作为主参考,音频和视频都向它对齐。这种方案适合音视频源来自不同时钟域的情况,实现复杂度高。
我面试时喜欢反着问:如果音视频差了几十毫秒,你会怎么办?很多人第一反应是直接丢弃帧。这其实是不对的,几十毫秒的偏移完全可以通过调整播放速率来纠正。Android 7.0之后可以通过AudioTrack.setPlaybackParams调整速度,ExoPlayer里也有PlaybackParameters可以实现变速不变调。只有在偏移超过几百毫秒、无法通过速率微调纠正时,才考虑清空缓冲重新对齐。
同步策略其实是播放器里最考功力的一部分,因为它涉及的不只是技术,还有用户主观体验。我在做播放器调优时总结了一个阈值经验:音频超前视频超过100ms,用户就能察觉口型不对;视频超前音频超过200ms,用户会明显觉得画面跳。所以同步算法里建议设置两个不同的阈值,一个用于触发等待,一个用于触发丢帧。
3.3 首屏秒开与卡顿优化,实战味道很浓的问题
首屏秒开几乎是每个音视频面试必问的实战题。面试官真正想听的是:你有没有从整个链路做起,而不是只盯着解码器。
首屏耗时可以拆成网络请求耗时、数据下载耗时、解封装耗时、解码首帧耗时、渲染首帧耗时几个环节。常规优化手段包括:
- DNS预解析和连接复用:针对点播URL做DNS缓存,减少HTTP DNS解析时间。
- 请求Range分片:不要等待整个文件头,提前读到moov box的位置。
- 解封装预热:部分格式支持从文件尾部读取索引信息,不用扫描全部数据。
- 视频参数集缓存:把SPS/PPS等参数提前缓存,解码器初始化阶段不用再等待码流解析。
- 预渲染:解码出第一帧后马上丢到Surface,不等第二帧。
还有一种更激进的方案是做预加载,比如在用户点击播放按钮前就提前缓存前几个分片。这个玩法我在短视频App里见过很多,用户上下滑的时候,候选视频已经被预下载了几百KB到几MB的数据,真正播放时首帧几乎秒出。
卡顿优化则是另一个大话题。网络卡顿要加缓存策略、段切换策略;解码卡顿要检查解码器实例是否需要重建、输入缓冲区设置是否合理;渲染掉帧要排查View类型和渲染链路。有一个容易被忽略的细节:MediaCodec硬解在某些设备上会默认输出到Surface而非ByteBuffer,如果后续要做OpenGL渲染,就必须把它配置为Surface输入,否则每帧拷回CPU内存的开销是灾难级的。
3.4 硬解与软解的选择,兼容性与功耗的平衡
硬解和软解是播放器设计里绕不开的方向。硬解是用GPU或专门的DSP解码,功耗低、性能高,但兼容性差,因为芯片厂商的驱动实现参差不齐。软解是用CPU跑FFmpeg解码,兼容性最好,但功耗高、发热大。
我的经验是:优先硬解,但必须设计自动降级机制。具体流程是:先用MediaCodec探支持能力,如果当前视频的Profile/Level不被支持,就自动切到软解。探支持能力不是写死一个list,而是调用MediaCodecList的getSupportedTypes,再配合MediaFormat的KEY_PROFILE和KEY_LEVEL做精确匹配。
软解选择FFmpeg解码时,值得注意的是多线程解码。FFmpeg对H.264/H.265都有多线程支持,在Android上要合理设置thread_count,过度增大线程数并不会线性提升性能,反而增加线程切换开销。另外软解内存足迹也要关注,软解1080p视频的缓冲区往往能撑到几十MB,低端机容易OOM,所以内存复用要做到极致。
硬解黑屏是一个特别常见的面试场景题,原因是Surface没有准备好就调用了start,或者BufferQueue的生产者消费者不匹配。排查方式通常是:先看MediaFormat里是否带CSD,再检查Surface是否有效,最后看SPS/PPS是否被正确传递。
4. 采集、渲染与推流,从本地播放延伸到实时场景
播放之外,采集和推流也是音视频面试的重要分支,尤其在直播、短视频、视频会议这类场景里。
4.1 Camera2 采集与预览链路
Camera2是Android相机的新架构,它把相机能力抽象成Pipeline,通过CaptureRequest控制每一帧的采集参数。做Camera2采集,最关键的是理解生命周期:设备打开、会话创建、捕获请求、重复请求关闭。
静态配置里有一组容易混淆的参数:PREVIEW、RECORD、MAXIMUM分别对应不同分辨率的缓冲区。简单说:预览用低分辨率降低延迟,录制用高分辨率保证画质,拍照用最大分辨率保留细节。如果你的应用需要同时支持这些场景,那就要配置多个CaptureSession或者做动态切换。
Camera2的采集回调有两种方式:ImageReader的Surface形式和CameraCaptureSession.CaptureCallback的Bytes形式。ImageReader非常适合配合MediaCodec的Surface输入直接做硬编,因为两者都在图形缓冲区上操作,省去一次拷贝。
面试追问里有个高频点:相机预览卡顿怎么排查?答案通常集中在三个方向:第一是ImageReader的maxImages设置太小导致队列阻塞,第二是GLSurfaceView的GL线程和Camera线程没有做背压,第三是SurfaceView切后台时没有及时release。这些坑我在实际项目中都踩过,逐一写上来的话能写一整篇。
4.2 音频采集、降噪与回声消除
音频采集主要用AudioRecord,它输出PCM数据,采样率、声道数、位深必须和编码器一致。很多面试者会忽略一个细节:AudioRecord的buffer大小不能随意设置,过小会导致丢数据,过大会增加延迟。建议用getMinBufferSize()查系统建议值再往上加。
做直播或实时通话,不得不面对降噪(NS)和回声消除(AEC)。Android自带的Acoustic Echo Canceler和Noise Suppressor可以解决一部分场景,但效果上限有限。更专业的方案是接第三方SDK,比如WebRTC的音频处理模块。
面试时如果聊到音频处理,常见的实战题是:为什么你在连麦房里听到自己的回声?原因是本机扬声器放出的对方声音被麦克风重新采集,如果不做AEC或者AEC参考信号延迟设置不对,对方的每一句话都会以你自己的声音形式回传回去。排查思路是抓取播放和采集两条音频流,对比延迟和频谱。
4.3 OpenGL ES 渲染与滤镜
滤镜是短视频、直播客户端最核心的视觉功能,而滤镜的底层一定绕不开OpenGL ES。面试官问滤镜,一般不是要你背API,而是想看你有没有理解渲染管线。
从相机采集到输出,整个渲染链路是:SurfaceTexture把Camera数据转成GL纹理,然后经过FBO(帧缓冲对象)做滤镜处理,最后渲染到屏幕。滤镜效果的本质是对纹理每个像素做片段着色器计算,比如灰度滤镜就是把RGB三通道加权求和,美颜滤镜则复杂得多,涉及边缘保留滤波和肤色检测。
GL纹理的坐标系和普通图片坐标系不一样,纹理坐标原点在左下角。新手最容易犯的错误是纹理方向不对导致画面上下颠倒,这也是相机预览常见的bug。解决方式是在绘制顶点前翻转纹理坐标。
FBO的使用也是滤镜工程里的关键点。你要对纹理做多级处理,不能直接在一个纹理上反复读写,需要创建一个离屏FBO作为中间层。多层滤镜串联时,每一层都需要独立的FBO,否则会出现不可预期的叠加效果。
4.4 直播推流协议选型:RTMP、HLS、SRT
直播推流协议也是面试官爱挖掘的点。RTMP基于TCP,适合国内直播,延迟2到5秒,兼容性好,但缺点是TCP在弱网下容易因为重传导致延迟升高。HLS基于HTTP,延迟一般在5到15秒,苹果生态和CDN支持好,但延迟难降。SRT基于UDP,专为弱网设计,具备前向纠错,延迟能控制在1秒内,适合实时性要求极高的场景。
如果面试官问你会怎么做直播选型,我一般会答:国内娱乐直播选RTMP,因为CDN成熟、播放端兼容性广;短视频点播走HLS,因为切片天然适合CDN;连麦或互动直播优先SRT,或者WebRTC。
有一个容易被忽略的点:推流不是只推视频轨,音频轨和视频轨的编码参数一定要匹配。常见的坑是音频采样率和视频GOP设置不一致,导致推流服务端切片时音视频不同步。还有就是推流要设置合理的编码码率上限,不然电视端观众收看时会反复缓冲。
WebRTC也是这两年音视频面试的必聊话题,但WebRTC和大多数客户端SDK不一样,它是为实时通信设计的,核心是P2P传输和拥塞控制。如果你只是在做直播,用WebRTC可能并不合适,因为它的媒体服务器部署成本和信令复杂度都很高。如果面试官问到,回答时可以先区分场景,再讲技术选型逻辑。
5. 高频追问与避坑经验,照着这个清单去梳
前面的内容都掌握后,最后一步是磨面试技巧。音视频面试的特点是面试官喜欢从一个点切入,然后层层深入,直到问到你不会为止。所以重要的不是背题,而是把每个回答变成可拓展的树状结构。
5.1 高频追问一:MediaCodec的InputBuffer里到底装的是什么
这是最容易翻车的问题之一。很多人答“装的是编码后的数据”,但这个答案太粗糙。实际上,MediaCodec的输入Buffer里装着的是封装层解出来的sample,它可能包含SPS/PPS这种参数集,也可能包含一帧完整的编码数据。更严格地说,MediaCodec需要一个完整AUs(Access Unit)作为一次输入。如果你把半个NALU塞进去,解码器直接报错。
这里有一个实操经验:很多H.264码流在输入MediaCodec之前需要把Annex-B格式转成AVCC格式。MediaCodec在Android API 21以前只接受AVCC,21以后能识别Annex-B里的起始码并做内部转换。所以如果你在低版本设备上硬解失败,优先检查码流封装格式。
5.2 高频追问二:硬解失败后怎么办
这个问题几乎没有标准答案,但面试官想听的是你的降级方案:
硬解失败的第一反应是捕获MediaCodec.CodecException,区分是资源不足、格式不支持还是驱动崩溃。资源不足可以释放其他实例后重试;格式不支持就直接切软解;驱动崩溃则需要做实例重建,有些情况下要重启进程才能恢复。
软解方案也不是简单的new一个解码器,需要考虑数据格式兼容、耗时控制、内存管控。我通常会建立一份“硬解失败视频特征”的日志表,记录分辨率、编码格式、Profile、Level、设备型号,方便后续做针对性适配。
5.3 常见问题排查速查表
整理一份我在项目里常见的音视频问题排查表,面试时能背出这些经验是非常加分的:
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| 视频起播黑屏 | 未传CSD/SPS/PPS;Surface未就绪 | 检查MediaFormat的csd-0/csd-1;打印Surface状态 |
| 音画不同步 | 时间戳换算错误;同步时钟选择不对 | 对比音视频PTS曲线,检查校准逻辑 |
| 硬解花屏/绿屏 | Profile/Level不支持;码流标头丢失 | 用MediaCodecList探测能力;抓流分析NALU |
| 播放卡顿 | 网络缓冲不足;解码器阻塞;掉帧策略不对 | 统计buffer水位、Jank/FPS曲线 |
| 内存持续上涨 | Buffer没释放;GL纹理泄漏 | 用Memory Profiler和OpenGL debug输出查纹理数量 |
| 声音断断续续 | AudioTrack缓冲区过小;回调线程阻塞 | 增大缓冲;检查AudioTrack回调耗时 |
| 推流延迟越拉越大 | TCP重传拥塞;GOP设置过大 | 改用UDP/SRT;调整GOP时长 |
| 首帧很慢 | 网络DNS慢;moov box在文件尾;解码器热启动慢 | DNS预解析;让服务端把moov移到文件头;解码器实例缓存 |
5.4 简历与复习建议
最后说说简历和复习策略。音视频方向的简历,我建议一定要写出能证明你做过的事,比如:实现的播放器能做到多少分辨率的硬解、首屏耗时控制在多少、推流延迟能达到多少秒。这些数字远比“熟悉音视频开发”有说服力。
复习的时候不要只刷题,按“底层原理—平台API—项目实战—优化案例”四个层次整理自己的知识树。每复习一个点,就尝试引导面试官朝你最熟悉的方向提问,比如你提到正在做直播,面试官大概率会追问推流协议、编码参数和延迟优化。提前准备好深挖的数据才是制胜关键。
我个人的体会有两点:一是音视频领域最值钱的能力不是背API,而是调试能力。出问题能快速定位到链路哪一环,比能默写十个函数名强得多。二是所有理论知识如果不能落到自己电脑上跑一遍,面试时一旦被连续追问就会露怯。建议找一段公开的MP4视频,用Android Studio起个项目,从MediaExtractor拉到MediaCodec硬解,再到AudioTrack和Surface播放,亲手跑通一个最简单的播放器,这套流程走完,面试中绝大多数问题都能接得住。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)