当你同时播放音乐、收到消息提示音、还有游戏背景音效,三路音频毫无干扰地同时从扬声器流出——这背后的魔法,就是本文的主角:AudioFlinger 混音机制。这也是整个音频子系统里最复杂的部分。前三篇我们分别看了 AudioFlinger 整体架构、AudioTrack 播放流程和 AudioRecord 录制流程。这一篇我们进入"引擎舱",看 AudioFlinger 如何同时处理几十路音频流:选择线程、混合数据、实时调度、音效处理,一气呵成。本文主要内容:PlaybackThread 四种类型:什么时候用哪种线程?Track 管理机制:Active/Inactive 状态、音量控制混音算法:多路 PCM 数据如何叠加?整数混音防溢出FastMixer 设计:SCHED_FIFO、Lock-free StateQueue、抖动优化AudioEffect 效果链:均衡器/混响/低音增强的挂载原理性能优化:CPU、延迟、功耗三角平衡源码版本:Android 15 AOSP关键路径:frameworks/av/services/audioflinger/Threads.cpp、Tracks.cpp、FastMixer.cpp、AudioMixer.cppPlaybackThread 四种类型AudioFlinger 不是用一个通用线程处理所有播放,而是根据场景选择最合适的线程类型。MixerThread:最常用的混音线程MixerThread是默认选择,绝大多数AudioTrack都跑在这里。它的核心能力是多路混音:把多个 Track 的 PCM 数据按比例相加,输出一路信号给 Audio HAL。MixerThread 数据流: Track A (44100Hz PCM) ──┐ Track B (16000Hz PCM) ──┤→ AudioMixer(重采样+混合) → HAL write() Track C (48000Hz PCM) ──┘何时创建 MixerThread?当AudioPolicyService决定路由到某个输出设备(如扬声器、耳机插孔)时,AudioFlinger 会为该设备创建一个 MixerThread。如果该设备已经有 MixerThread,新的 Track 直接加入现有线程。DirectOutputThread:直通输出DirectOutputThread的特点是绕过软件混音器,把 PCM(或编码格式)数据直接交给 HAL。// frameworks/av/services/audioflinger/Threads.cpp// DirectOutputThread 不创建 AudioMixerAudioFlinger::DirectOutputThread::DirectOutputThread(...):PlaybackThread(audioFlinger,output,id,type){// 注意:没有 mAudioMixer = new AudioMixer(...)}适用场景:HiFi 高清播放:需要保证信号路径干净,不经过软件混音引入失真蓝牙 A2DP:编码格式(SBC/AAC/LDAC)由 HAL 处理,上层只传 PCM空间音频:需要精确控制声道映射代价:同时只能有一个 Track(不支持混音),第二个 Track 请求时会被拒绝或降级到 MixerThread。OffloadThread:硬件解码卸载OffloadThread把音频解码工作交给硬件 DSP,主 CPU 几乎不参与处理。// 检查设备是否支持 OffloadboolAudioFlinger::isOffloadSupported(constaudio_offload_info_tinfo){returnAudioSystem::isOffloadSupported(info);// 底层查询 HAL 的 is_offload_supported()}典型使用:锁屏播放音乐时,Offload 允许 AP 进入深度睡眠,由 DSP 单独运行,续航可增加 20-30%。Android 15 中的变化:Offload Track 现在支持AudioEffect(通过 HAL 侧的效果处理),而之前版本的 Offload 完全不支持效果链。DuplicatingThread:重复输出DuplicatingThread继承自MixerThread,它可以同时输出到多个设备:// DuplicatingThread 持有多个输出目标classDuplicatingThread:publicMixerThread{SortedVectorOutputTrack*mOutputTracks;// 多个输出// threadLoop 写数据时,遍历所有 OutputTrackvoidthreadLoop_write(){for(autooutputTrack:mOutputTracks){outputTrack-write(mSinkBuffer,mNormalFrameCount);}}};典型场景:车载系统同时输出音乐到扬声器和蓝牙耳机,或开发者通过AudioRecord同时录制麦克风和正在播放的音乐(需要系统权限)。线程选择逻辑// AudioFlinger::openOutput() 中的线程选择spPlaybackThreadAudioFlinger::openOutput_l(...,audio_output_flags_t flags){spPlaybackThreadthread;if(flagsAUDIO_OUTPUT_FLAG_DIRECT){// 请求直通 → DirectOutputThreadthread=newDirectOutputThread(this,output,id,...);}elseif(flagsAUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD){// 请求 Offload → OffloadThreadthread=newOffloadThread(this,output,id,...);}else{// 默认 → MixerThread(包含 FastMixer 支持)thread=newMixerThread(this,output,id,...);}mPlaybackThreads.add(id,thread);returnthread;}Track 管理机制Track 的生命周期状态每个 Track 在 PlaybackThread 内部有独立的状态机:IDLE(创建后初始) ↓ AudioTrack.play() ACTIVE(加入混音队列) ↓ AudioTrack.pause() PAUSING → PAUSED(等待当前数据处理完) ↓ AudioTrack.stop() STOPPING_1 → STOPPING_2 → STOPPED(等待数据播完) ↓ AudioFlinger 发现 Track 停止 FLUSHED → IDLE(缓冲区清空)// frameworks/av/services/audioflinger/Tracks.cpp// Track 的核心状态转换voidAudioFlinger::PlaybackThread::Track::setState(State state){// 状态转换中会更新 mState 并通知 PlaybackThreadMutex::Autolock_l(mLock);mState=state;mCblk-mFutex++;// 通过 futex 通知消费者}TrackHandle:跨进程代理App 持有的IAudioTrack实际上是TrackHandle,它是Binder服务端:// AudioFlinger 端classTrackHandle:publicandroid::BnAudioTrack{spPlaybackThread::TrackmTrack;// App 调用 start() → Binder → TrackHandle::start()status_tstart()override{returnmTrack-start();}// App 调用 setVolume() → 但实际上 App 直接写 mCblk-mVolumeLR// 不需要经过 Binder!这是一个关键的性能优化};音量控制的两种路径这是 Track 管理里最精妙的设计:// 路径1:快速音量(共享内存直接写,无 Binder IPC)// AudioTrack.setStereoVolume() 直接修改 mCblk-mVolumeLRvoidAudioTrack::setStereoVolume(floatleft,floatright){mCblk-setVolumeLR(gain_from_float(left),gain_from_float(right));}// 路
AudioFlinger混音机制深度解析
当你同时播放音乐、收到消息提示音、还有游戏背景音效,三路音频毫无干扰地同时从扬声器流出——这背后的魔法,就是本文的主角:AudioFlinger 混音机制。这也是整个音频子系统里最复杂的部分。前三篇我们分别看了 AudioFlinger 整体架构、AudioTrack 播放流程和 AudioRecord 录制流程。这一篇我们进入"引擎舱",看 AudioFlinger 如何同时处理几十路音频流:选择线程、混合数据、实时调度、音效处理,一气呵成。本文主要内容:PlaybackThread 四种类型:什么时候用哪种线程?Track 管理机制:Active/Inactive 状态、音量控制混音算法:多路 PCM 数据如何叠加?整数混音防溢出FastMixer 设计:SCHED_FIFO、Lock-free StateQueue、抖动优化AudioEffect 效果链:均衡器/混响/低音增强的挂载原理性能优化:CPU、延迟、功耗三角平衡源码版本:Android 15 AOSP关键路径:frameworks/av/services/audioflinger/Threads.cpp、Tracks.cpp、FastMixer.cpp、AudioMixer.cppPlaybackThread 四种类型AudioFlinger 不是用一个通用线程处理所有播放,而是根据场景选择最合适的线程类型。MixerThread:最常用的混音线程MixerThread是默认选择,绝大多数AudioTrack都跑在这里。它的核心能力是多路混音:把多个 Track 的 PCM 数据按比例相加,输出一路信号给 Audio HAL。MixerThread 数据流: Track A (44100Hz PCM) ──┐ Track B (16000Hz PCM) ──┤→ AudioMixer(重采样+混合) → HAL write() Track C (48000Hz PCM) ──┘何时创建 MixerThread?当AudioPolicyService决定路由到某个输出设备(如扬声器、耳机插孔)时,AudioFlinger 会为该设备创建一个 MixerThread。如果该设备已经有 MixerThread,新的 Track 直接加入现有线程。DirectOutputThread:直通输出DirectOutputThread的特点是绕过软件混音器,把 PCM(或编码格式)数据直接交给 HAL。// frameworks/av/services/audioflinger/Threads.cpp// DirectOutputThread 不创建 AudioMixerAudioFlinger::DirectOutputThread::DirectOutputThread(...):PlaybackThread(audioFlinger,output,id,type){// 注意:没有 mAudioMixer = new AudioMixer(...)}适用场景:HiFi 高清播放:需要保证信号路径干净,不经过软件混音引入失真蓝牙 A2DP:编码格式(SBC/AAC/LDAC)由 HAL 处理,上层只传 PCM空间音频:需要精确控制声道映射代价:同时只能有一个 Track(不支持混音),第二个 Track 请求时会被拒绝或降级到 MixerThread。OffloadThread:硬件解码卸载OffloadThread把音频解码工作交给硬件 DSP,主 CPU 几乎不参与处理。// 检查设备是否支持 OffloadboolAudioFlinger::isOffloadSupported(constaudio_offload_info_tinfo){returnAudioSystem::isOffloadSupported(info);// 底层查询 HAL 的 is_offload_supported()}典型使用:锁屏播放音乐时,Offload 允许 AP 进入深度睡眠,由 DSP 单独运行,续航可增加 20-30%。Android 15 中的变化:Offload Track 现在支持AudioEffect(通过 HAL 侧的效果处理),而之前版本的 Offload 完全不支持效果链。DuplicatingThread:重复输出DuplicatingThread继承自MixerThread,它可以同时输出到多个设备:// DuplicatingThread 持有多个输出目标classDuplicatingThread:publicMixerThread{SortedVectorOutputTrack*mOutputTracks;// 多个输出// threadLoop 写数据时,遍历所有 OutputTrackvoidthreadLoop_write(){for(autooutputTrack:mOutputTracks){outputTrack-write(mSinkBuffer,mNormalFrameCount);}}};典型场景:车载系统同时输出音乐到扬声器和蓝牙耳机,或开发者通过AudioRecord同时录制麦克风和正在播放的音乐(需要系统权限)。线程选择逻辑// AudioFlinger::openOutput() 中的线程选择spPlaybackThreadAudioFlinger::openOutput_l(...,audio_output_flags_t flags){spPlaybackThreadthread;if(flagsAUDIO_OUTPUT_FLAG_DIRECT){// 请求直通 → DirectOutputThreadthread=newDirectOutputThread(this,output,id,...);}elseif(flagsAUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD){// 请求 Offload → OffloadThreadthread=newOffloadThread(this,output,id,...);}else{// 默认 → MixerThread(包含 FastMixer 支持)thread=newMixerThread(this,output,id,...);}mPlaybackThreads.add(id,thread);returnthread;}Track 管理机制Track 的生命周期状态每个 Track 在 PlaybackThread 内部有独立的状态机:IDLE(创建后初始) ↓ AudioTrack.play() ACTIVE(加入混音队列) ↓ AudioTrack.pause() PAUSING → PAUSED(等待当前数据处理完) ↓ AudioTrack.stop() STOPPING_1 → STOPPING_2 → STOPPED(等待数据播完) ↓ AudioFlinger 发现 Track 停止 FLUSHED → IDLE(缓冲区清空)// frameworks/av/services/audioflinger/Tracks.cpp// Track 的核心状态转换voidAudioFlinger::PlaybackThread::Track::setState(State state){// 状态转换中会更新 mState 并通知 PlaybackThreadMutex::Autolock_l(mLock);mState=state;mCblk-mFutex++;// 通过 futex 通知消费者}TrackHandle:跨进程代理App 持有的IAudioTrack实际上是TrackHandle,它是Binder服务端:// AudioFlinger 端classTrackHandle:publicandroid::BnAudioTrack{spPlaybackThread::TrackmTrack;// App 调用 start() → Binder → TrackHandle::start()status_tstart()override{returnmTrack-start();}// App 调用 setVolume() → 但实际上 App 直接写 mCblk-mVolumeLR// 不需要经过 Binder!这是一个关键的性能优化};音量控制的两种路径这是 Track 管理里最精妙的设计:// 路径1:快速音量(共享内存直接写,无 Binder IPC)// AudioTrack.setStereoVolume() 直接修改 mCblk-mVolumeLRvoidAudioTrack::setStereoVolume(floatleft,floatright){mCblk-setVolumeLR(gain_from_float(left),gain_from_float(right));}// 路