安卓音频子系统之AudioFlinger
AudioFlinger简称AF是Android原生音频系统Native层核心服务运行于audioserver进程是衔接上层应用音频请求与底层硬件驱动的中枢。它承担App音频数据、管理音频流、混音、重采样、路由、格式转换、音效、流管控等核心职责相当于系统级音频混音台统筹多音源并发播放与录制保障音频输出稳定无冲突。一、核心源码AudioFlinger源码集中于AOSPframeworks/av/services/audioflinger/核心文件及功能AudioFlinger.cpp/h服务入口、全局管理、Binder对外接口实现Threads.cpp/h播放/录制/混音线程核心逻辑Tracks.cpp/h音频流Track对象生命周期管理AudioMixer.cpp/h混音、格式转换、音量控制核心实现FastMixer.cpp/h低延迟无锁混音模块源码frameworks/av/media/libaudioclient/是客户端库包含IAudioFlinger接口定义。二、AudioFlinger核心线程体系AudioFlinger采用线程隔离设计不同类型的音频任务播放、录制、混音由独立线程处理避免任务阻塞互相影响。所有的工作都在派生自ThreadBase的线程中完成。关键类继承关系ThreadBase 基础线程类。PlaybackThread 所有播放线程的基类。MixerThread 最常用的混音线程。AudioMixer 负责实际的混音算法。DirectOutputThread 用于低延迟、不混音的直通输出。OffloadThread 用于音频硬解Direct Path / Offload绕过 AudioFlinger 混音。RecordThread 录制线程。针对其中的播放场景对三个主要的线程进行详细功能和职责说明1. PlaybackThread播放线程基类抽象层PlaybackThread是所有音频播放线程的父类属于抽象线程封装了播放线程的通用逻辑音频数据读取、缓冲区管理、HAL交互、设备控制。它不直接处理具体音频流而是定义播放线程的标准工作流程子类负责实现差异化逻辑。核心职责管理音频输出流Track维护Track列表与AudioHAL对接通过write接口将混音后的数据写入硬件处理音频设备切换、音量调节、暂停/恢复等控制指令。2. MixerThread混音线程核心工作线程MixerThread继承自PlaybackThread是最核心、最常用的播放线程也是处理多音频流混音的关键。系统默认的音乐、铃声、通知等音频流都会交由MixerThread处理。核心职责拉取多个App的Track音频数据执行多路混音对音频数据做重采样、位深转换、声道映射适配硬件格式将处理后的音频数据写入硬件缓冲区驱动发声。3. DirectOutputThread/OffloadThread直出/解码线程这两类线程同样继承自PlaybackThread针对特殊音频场景做优化跳过混音环节提升播放效率DirectOutputThread直出线程适用于低延迟场景如通话、游戏音频不参与系统混音音频数据直接下发HAL减少处理链路OffloadThread硬解线程将音频解码工作交给硬件Codec执行如播放MP3/AAC降低CPU占用适用于长时音乐播放。除此之外播放类的线程还有FastMixerThread低延迟无锁混音跳过冗余处理直连硬件多用于游戏音效、实时语音、乐器类APP、DuplicatingThread音频流复制分发多设备同步输出例如扬声器耳机/蓝牙同步发声AudioFlinger还有RecordThread录制线程负责从麦克风等输入设备采集PCM数据分发至上层RecordTrack支持多路录制管控、回声消除、降噪预处理、MmapThread低延迟内存映射线程。本篇聚焦播放核心重点讲解MixerThread混音逻辑。三、音频流载体Track与TrackHandle在理解混音前必须先搞懂Track——它是AudioFlinger中音频流的最小载体每个App播放音频都会对应AudioFlinger中的一个Track实例。1. Track的层级关系App层AudioTrack实例通过共享内存SharedMemory与底层通信AudioFlinger层TrackHandle客户端代理 Track服务端实体一一对应Track隶属于特定MixerThread由Thread统一管理生命周期。2. Track核心工作流程App调用AudioTrack.write()将PCM数据写入共享内存AudioFlinger创建Track绑定到对应MixerThreadMixerThread循环拉取Track中的音频数据数据处理完成后清空Track缓冲区等待App写入新数据。简单理解一个Track 一路音频流MixerThread负责把多路Track混合成一路。// frameworks/av/services/audioflinger/AudioFlinger.cpp spIAudioTrack AudioFlinger::createTrack(...) { ... // 1. 根据传入的 output来自 AudioPolicyService找到对应的 PlaybackThread spPlaybackThread thread checkPlaybackThread_l(output); ... // 2. 在目标线程上创建 Track track thread-createTrack_l(client, attr, sharedBuffer, sampleRate,...); ... // 3. 返回一个 IAudioTrack 的 Binder 对象给客户端 return track; }数据共享机制客户端和 AudioFlinger 通过共享内存IMemory/MemoryDealer交换音频数据避免拷贝。Track对象内部维护着共享缓冲区的状态。// Track 中关键的内存管理类 spMemoryDealer mMemoryDealer; spIMemory mCblkMemory; // 指向控制块和缓冲区的共享内存 audio_track_cblk_t* mCblk; // 控制块指针用于同步如读写位置四、核心MixerThread混音全流程拆解混音是AudioFlinger的核心能力解决多App同时播放音频的冲突问题。MixerThread采用固定周期循环工作模式循环周期由硬件采样率决定如48kHz采样率对应约20.8ms一次循环每一轮循环完成一次混音硬件写入。整体流程如下步骤 1准备阶段 -prepareTracks_l这个阶段在锁内进行主要目的是收集本轮循环需要处理的活跃 Track并计算每个 Track 可读取的音频数据量。源码位置frameworks/av/services/audioflinger/Threads.cpp// MixerThread::prepareTracks_l 函数的关键逻辑 void AudioFlinger::MixerThread::prepareTracks_l( VectorspTrack *tracksToRemove // 输出参数需要移除的Track ) { mActiveTracks.clear(); // 清空上一轮的活跃Track列表 mActiveTracks.reserve(mTracks.size()); // 遍历该线程上的所有Track for (const spTrack t : mTracks) { Track* const track t.get(); // 检查Track状态是否活跃、是否暂停、是否已停止等 if (track-isInvalid() || track-isStopped() || track-isPausing()) { ... // 处理非活跃状态将其加入tracksToRemove continue; } // --- 关键流控与帧数计算 --- // 获取该Track的共享控制块 audio_track_cblk_t* cblk track-cblk(); // 1. 检查Track是否有可读数据消费者位置mRear是否落后于生产者位置mFront if (cblk-framesAvailable_l() 0) { ... // 处理underrun数据不足 continue; } // 2. 计算本次混音周期内可以从这个Track读取的最大帧数 // 这取决于a) Track缓冲区中的可用帧数 b) 本次混音循环需要输出的帧数mFrameCount size_t framesReady cblk-framesReady(); size_t framesToMix min(framesReady, mFrameCount); if (framesToMix 0) { ... // 没有数据可读跳过 continue; } // 3. 设置音量、重采样等信息并准备AudioMixer // 将Track的缓冲区信息共享内存地址和本次要混音的帧数设置到AudioMixer中 mAudioMixer-setBuffer(track-name(), track-mainBuffer()); mAudioMixer-setParameter(track-name(), AudioMixer::RESAMPLE, ...); mAudioMixer-setParameter(track-name(), AudioMixer::VOLUME, ...); // 4. 将该Track加入本轮混音的活跃列表 mActiveTracks.add(track); } }此阶段的核心输出mActiveTracks 一个包含所有在本轮循环中需要被混音的 Track 指针的向量。每个活跃 Track 在AudioMixer中的配置已更新如缓冲区地址、音量、重采样器。步骤 2混音阶段 -threadLoop_mix这个阶段是性能最关键的部分它调用AudioMixer将多个 Track 的 PCM 数据混合到一个单一的缓冲区中。源码位置frameworks/av/services/audioflinger/Threads.cpp和 frameworks/av/media/libaudioprocessing/AudioMixer.cppvoid AudioFlinger::MixerThread::threadLoop_mix() { ... // 1. 准备混音清空输出缓冲区获取需要混音的活跃 Track 列表 mAudioMixer-process(...); ... }AudioMixer继承自AudioMixerBaseAudioMixerBase.h中定义process方法如下//AudioMixerBase.h void process() { preProcess(); (this-*mHook)(); postProcess(); }void AudioMixer::preProcess() { for (const auto pair : mTracks) { // Clear contracted buffer before processing if contracted channels are saved const std::shared_ptrTrackBase tb pair.second; Track *t static_castTrack*(tb.get()); if (t-mKeepContractedChannels) { t-clearContractedBuffer(); } t-clearTeeFrameCopied(); } } void AudioMixer::postProcess() { // Process haptic data. // Need to keep consistent with VibrationEffect.scale(int, float, int) for (const auto pair : mGroups) { // process by group of tracks with same output main buffer. const auto group pair.second; for (const int name : group) { const std::shared_ptrTrack t getTrack(name); if (t-mHapticPlaybackEnabled) { size_t sampleCount mFrameCount * t-mMixerHapticChannelCount; uint8_t* buffer (uint8_t*)pair.first mFrameCount * audio_bytes_per_frame( t-mMixerChannelCount, t-mMixerFormat); switch (t-mMixerFormat) { // Mixer format should be AUDIO_FORMAT_PCM_FLOAT. case AUDIO_FORMAT_PCM_FLOAT: { os::scaleHapticData((float*) buffer, sampleCount, t-mHapticIntensity,t-mHapticMaxAmplitude); } break; default: LOG_ALWAYS_FATAL(bad mMixerFormat: %#x, t-mMixerFormat); break; } break; } if (t-teeBuffer ! nullptr t-volumeRL 0) { // Need to mute tee memset(t-teeBuffer, 0, t-mTeeBufferFrameCount * t-mInputFrameSize); } } } }关键是this-*mHook调用预设置的混音函数钩子hook这个hook在prepareTracks_l阶段根据Track的格式16bit/float、通道数、是否需要重采样等预先设定好。 例如可能是 track__16BitsStereo, track__GenericResample, track__NoResampleStereo 等。混音函数钩子示例track__genericResample这个函数处理需要重采样的最常见情况。void AudioMixerBase::TrackBase::track__genericResample( int32_t* out, size_t outFrameCount, int32_t* temp, int32_t* aux) { ALOGVV(track__genericResample\n); // 详细调试日志 // 更新重采样器的目标采样率 // 因为音轨的采样率可能在运行时改变如播放不同采样率的文件 mResampler-setSampleRate(sampleRate); // --- 情况1存在辅助输出aux ! NULL--- // 当需要将音频发送到效果器总线时进入此分支 if (aux ! NULL) { // 对于发送到辅助缓冲区的音频先以单位增益进行重采样 // 这样可以在重采样后单独应用发送电平控制 mResampler-setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT); // 清空临时缓冲区确保从干净的状态开始累加 memset(temp, 0, outFrameCount * mMixerChannelCount * sizeof(int32_t)); // 核心重采样操作从bufferProvider读取数据重采样后写入temp缓冲区 mResampler-resample(temp, outFrameCount, bufferProvider); // 检查是否需要音量渐变ramp或辅助发送电平渐变 // CC_UNLIKELY是编译器宏提示该条件为假的可能性更大帮助CPU分支预测 if (CC_UNLIKELY(volumeInc[0]|volumeInc[1]|auxInc)) { // 需要渐变分别对主输出和辅助输出应用渐变的音量/发送电平 volumeRampStereo(out, outFrameCount, temp, aux); } else { // 恒定增益直接应用固定的音量和发送电平 volumeStereo(out, outFrameCount, temp, aux); } } // --- 情况2无辅助输出aux NULL--- else { // 检查是否需要主音量渐变 if (CC_UNLIKELY(volumeInc[0]|volumeInc[1])) { // 需要音量渐变先以单位增益重采样到临时缓冲区 mResampler-setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT); memset(temp, 0, outFrameCount * MAX_NUM_CHANNELS * sizeof(int32_t)); mResampler-resample(temp, outFrameCount, bufferProvider); // 然后对临时缓冲区的数据应用音量渐变混合到主输出 volumeRampStereo(out, outFrameCount, temp, aux); } // 恒定增益情况无音量渐变 else { // 直接设置重采样器的目标音量重采样过程中同时应用音量缩放 // 这是最高效的路径重采样和音量应用一步完成 mResampler-setVolume(mVolume[0], mVolume[1]); // 重采样并直接输出到主缓冲区同时应用恒定音量 mResampler-resample(out, outFrameCount, bufferProvider); } } }设计精髓通过预先为每个 Track 设置好最合适的、高度优化的特定函数hookAudioMixer避免了在热路径process循环中进行大量的条件判断if-else这是其高性能的关键。步骤 3效果器处理在数据混合之后可能会对整体的混合输出应用音频效果如均衡器、压缩器等。// 检查条件如果不需要睡眠mSleepTimeUs 0且不是OFFLOAD线程类型 if (mSleepTimeUs 0 mType ! OFFLOAD) { // 遍历该线程上的所有音频效果链effectChains for (size_t i 0; i effectChains.size(); i ) { // 核心处理对当前效果链应用所有音频效果 // process_l() 会依次调用效果链中各个效果器的process函数 // 通常将数据从inBuffer处理到outBuffer effectChains[i]-process_l(); // TODO: 未来优化项 - 当混音时直接将触觉数据写入接收缓冲区 // 检查是否存在活跃的触觉会话并且当前效果链的会话ID匹配该触觉会话 if (activeHapticSessionId ! AUDIO_SESSION_NONE activeHapticSessionId effectChains[i]-sessionId()) { // 当前会话包含触觉数据需要特殊处理 // 确定触觉会话的音频通道数 // 如果效果缓冲区有效mEffectBufferValid为true使用混音器的通道掩码计算通道数 // 否则使用默认的通道数mChannelCount uint32_t hapticSessionChannelCount mEffectBufferValid ? audio_channel_count_from_out_mask(mMixerChannelMask) : mChannelCount; // 特殊处理如果是空间音频SPATIALIZER类型且触觉会话未被空间化 if (mType SPATIALIZER !isHapticSessionSpatialized) { // 使用默认通道数避免空间化处理影响触觉数据 hapticSessionChannelCount mChannelCount; } // 计算音频数据部分在缓冲区中的大小字节数 // 公式帧数 × 每帧的字节数通道数 × 每个样本的字节数 const size_t audioBufferSize mNormalFrameCount * audio_bytes_per_frame(hapticSessionChannelCount, EFFECT_BUFFER_FORMAT); // 关键操作复制触觉数据 // 将触觉数据直接从输入缓冲区复制到输出缓冲区绕过效果处理 // 这是因为触觉数据如振动反馈不应该被音频效果如均衡器、混响等修改 // memcpy_by_audio_format: 按音频格式进行内存复制处理不同样本格式的转换 memcpy_by_audio_format( // 目标地址效果链输出缓冲区的触觉数据部分 // 位置 输出缓冲区基地址 音频数据大小跳过音频部分 (uint8_t*)effectChains[i]-outBuffer() audioBufferSize, EFFECT_BUFFER_FORMAT, // 效果缓冲区格式如16位PCM、浮点数等 // 源地址效果链输入缓冲区的触觉数据部分 (const uint8_t*)effectChains[i]-inBuffer() audioBufferSize, EFFECT_BUFFER_FORMAT, // 要复制的数据量帧数 × 触觉通道数 mNormalFrameCount * mHapticChannelCount); } } }步骤 4写入 HAL -threadLoop_write这是混音流水线的最后一步将处理好的最终音频数据发送给音频硬件。// MixerThread::threadLoop_write ssize_t AudioFlinger::MixerThread::threadLoop_write() { return PlaybackThread::threadLoop_write(); } //核心代码 ssize_t AudioFlinger::PlaybackThread::threadLoop_write() { // 计算时间戳等元数据 ... // 关键调用通过Audio HAL接口将数据写入硬件 bytesWritten mOutput-write((char *)mSinkBuffer offset, mBytesRemaining); ... return bytesWritten; }mOutput-write调用栈这最终会通过AudioStreamOutHAL 接口调用到厂商实现的 HAL 代码将mMixBuffer中的数据写入 DMA 缓冲区随后音频编解码器会将这些数字信号转换为模拟信号驱动扬声器或耳机。总结MixerThread 的混音流程是一个精心设计的、高效的流水线准备 (prepareTracks_l):决策阶段。在锁内决定“谁”哪些Track和“多少”多少帧数据参与本轮混音。这是流控的核心。混音 (threadLoop_mix-AudioMixer::process):执行阶段。无锁或细粒度锁地执行高度优化的、预编译的混音函数将数据累加到输出缓冲区。这是性能的瓶颈和优化的重点。效果 (threadLoop_effects):后处理阶段。对混合后的流施加全局音频效果。写入 (threadLoop_write):输出与反馈阶段。将最终数据推送至硬件并通知客户端数据已被消费释放缓冲区空间。五、线程调度与优先级AudioFlinger中的关键音频线程如MixerThread、DirectOutputThread等具有较高的执行优先级配置线程优先级通过set_sched_policy(pid, SP_AUDIO_APP)设置为音频调度策略对应较高的实时优先级通常为-16确保音频数据处理不被低优先级任务中断调度策略多数情况下采用SCHED_FIFO实时调度策略保证音频线程在就绪时能立即获得CPU时间资源保障作为系统关键服务线程享有内存和CPU调度的优先保障但栈空间分配与普通线程差异不大主要依赖高效的缓冲区管理来处理音频数据流。六、常见问题排查1. 音频卡顿/爆音排查思路检查MixerThread线程是否阻塞通过logcat过滤AudioFlinger日志查看线程循环耗时排查Track数据供给不足App写入速度慢于线程读取速度导致缓冲区空检查CPU占用audioserver进程CPU过高线程调度延迟验证混音削波多App同时播放时音量叠加过大导致削波爆音。2. 调试命令快速定位线程问题# 查看audioserver线程状态与优先级adb shell ps -t -o PID,TID,PRIO,NAME | grep audioserver# 抓取AudioFlinger详细日志adb logcat -s AudioFlinger -v time# 查看音频线程耗时adb shell dumpsys audio flinger