一、PlaybackThread::threadLoop_write
1.变量
mFramesWritten
类型: int64_t
作用: 记录从线程启动以来已写入音频设备的帧数(不包括挂起状态下的写入)
mSuspendedFrames
类型: int64_t
作用: 记录线程在挂起(suspended)状态下模拟写入的帧数
mBytesRemaining
作用: 用于实时跟踪待写入的剩余字节数。初始化时等于mCurrentWriteLength,表示本轮需写入的总量;写入过程中递减,直至归零
mCurrentWriteLength
混音模式:threadLoop_mix()计算混音数据后,设置mCurrentWriteLength为需写入的字节数。
直通模式:当需要立即写入时,mCurrentWriteLength设为mSinkBufferSize(硬件接收缓冲区大小)
2.代码段
mBytesRemaining !=0
if (mBytesRemaining) {// 如果还有未写入的字节数据(mBytesRemaining > 0)// FIXME: 重构代码以减少系统调用次数(当前实现可能效率不高)const int64_t lastIoBeginNs = systemTime(); // 记录开始写入操作的时间点(纳秒)ret = threadLoop_write(); // 执行实际的音频数据写入操作const int64_t lastIoEndNs = systemTime(); // 记录写入操作完成的时间点(纳秒)if (ret < 0) {// 写入失败(ret < 0)mBytesRemaining = 0; // 清零剩余字节数(标记写入失败)} else if (ret > 0) {// 成功写入部分数据(ret > 0)mBytesWritten += ret; // 更新总写入字节数mBytesRemaining -= ret; // 减少剩余待写入字节数const int64_t frames = ret / mFrameSize; // 计算实际写入的音频帧数mFramesWritten += frames; // 更新总写入帧数writePeriodNs = lastIoEndNs - mLastIoEndNs; // 计算本次写入操作的时间间隔(纳秒)// 仅对线性PCM格式音频进行处理(帧大小固定)if (audio_has_proportional_frames(mFormat)) {// 检查是否处于连续混音周期(混音器状态就绪且是连续写入)if (mMixerStatus == MIXER_TRACKS_READY &&loopCount == lastLoopCountWritten + 1) {// 计算抖动(jitter):实际写入时间与理论时间的偏差const double jitterMs =TimestampVerifier<int64_t, int64_t>::computeJitterMs({frames, writePeriodNs}, // 本次写入的帧数和时间间隔{0, 0} /* 上次时间戳 */, // 首次计算使用零值mSampleRate); // 音频采样率// 计算处理时间(从上次写入结束到本次写入开始的时间间隔)const double processMs = (lastIoBeginNs - mLastIoEndNs) * 1e-6;// 加锁修改线程内部状态audio_utils::lock_guard _l(mutex());mIoJitterMs.add(jitterMs); // 记录抖动数据用于统计mProcessTimeMs.add(processMs); // 记录处理时间用于统计// 如果使用MonoPipe(环形缓冲区)if (mPipeSink.get() != nullptr) {MonoPipe* monoPipe = static_cast<MonoPipe*>(mPipeSink.get());const ssize_t availableToWrite = mPipeSink->availableToWrite(); // 获取可写空间const size_t pipeFrames = monoPipe->maxFrames(); // 管道总容量(帧)// 计算管道中已占用的帧数(总容量 - 可写空间)const size_t remainingFrames = pipeFrames - max(availableToWrite, 0);mMonopipePipeDepthStats.add(remainingFrames); // 记录管道使用深度}}// 检测写入阻塞(耗时过长)const int64_t deltaWriteNs = lastIoEndNs - lastIoBeginNs; // 实际写入耗时if ((mType == MIXER || mType == SPATIALIZER) && // 仅检查混音/空间音频线程deltaWriteNs > maxPeriod) { // 超过最大允许时间mNumDelayedWrites++; // 增加延迟写入计数// 避免频繁告警(超过告警间隔才触发)if ((lastIoEndNs - lastWarning) > kWarningThrottleNs) {ATRACE_NAME("underrun"); // 性能分析标记ALOGW("write blocked for %lld msecs, %d delayed writes, thread %d",(long long)deltaWriteNs / NANOS_PER_MILLISECOND, // 纳秒转毫秒mNumDelayedWrites, // 延迟计数mId); // 线程IDlastWarning = lastIoEndNs; // 更新最后告警时间}}}// 更新时间追踪变量mLastIoBeginNs = lastIoBeginNs; // 更新最后写入开始时间mLastIoEndNs = lastIoEndNs; // 更新最后写入结束时间lastLoopCountWritten = loopCount; // 更新最后成功写入的循环计数}
}
mBytesRemaining == 0
// 检查当前没有剩余字节需要写入(mBytesRemaining == 0)
if (mBytesRemaining == 0) {// 重置当前写入长度为0mCurrentWriteLength = 0;// 如果混音器状态为TRACKS_READY(有活跃音轨且数据就绪)if (mMixerStatus == MIXER_TRACKS_READY) {// 执行混音操作:将各音轨数据混合到混音缓冲区// 同时会设置mCurrentWriteLength为实际写入长度threadLoop_mix();} // 如果混音器状态不是DRAIN_TRACK或DRAIN_ALL(非排空状态)else if ((mMixerStatus != MIXER_DRAIN_TRACK) && (mMixerStatus != MIXER_DRAIN_ALL)) {// 计算线程需要休眠的时间(可能为0)threadLoop_sleepTime();// 如果休眠时间为0(需要立即写入)if (mSleepTimeUs == 0) {// 设置当前写入长度为整个接收缓冲区大小mCurrentWriteLength = mSinkBufferSize;// 统计所有活跃音轨的欠载(underrun)情况:for (const auto& track : activeTracks) {// 只处理活跃且未停止/暂停/终止的音轨if (track->fillingStatus() == IAfTrack::FS_ACTIVE&& !track->isStopped()&& !track->isPaused()&& !track->isTerminated()) {// 记录因线程休眠导致的欠载ALOGV("%s: track(%d) %s underrun due to thread sleep of %zu frames",__func__, track->id(), track->getTrackStateAsString(),mNormalFrameCount);// 在音轨代理中累加欠载帧数track->audioTrackServerProxy()->tallyUnderrunFrames(mNormalFrameCount);}}}}// 以下处理混音后的数据:// ----------------------------------------------------------------// 检查混音缓冲区是否有效且需要复制到效果缓冲区或接收缓冲区if (mMixerBufferValid && (mEffectBufferValid || !mHasDataCopiedToSinkBuffer)) {// 确定目标缓冲区(效果缓冲区优先,否则直接到接收缓冲区)void *buffer = mEffectBufferValid ? mEffectBuffer : mSinkBuffer;// 确定目标格式(效果缓冲区格式优先,否则使用设备格式)audio_format_t format = mEffectBufferValid ? mEffectBufferFormat : mFormat;// 如果不需要效果处理(直通模式),需额外处理:if (!mEffectBufferValid) {// 单声道混合处理(如果需要)if (requireMonoBlend()) {mono_blend(mMixerBuffer, mMixerBufferFormat, mChannelCount,mNormalFrameCount, true /*limit*/);}// 声道平衡处理(如果没有FastMixer)if (!hasFastMixer()) {mBalance.setBalance(mMasterBalance.load());mBalance.process((float *)mMixerBuffer, mNormalFrameCount);}}// 格式转换:将混音缓冲区的数据转换格式后复制到目标缓冲区memcpy_by_audio_format(buffer, format, mMixerBuffer, mMixerBufferFormat,mNormalFrameCount * (mixerChannelCount + mHapticChannelCount));// 特殊处理:如果有触觉通道且直通输出if (!mEffectBufferValid && mHapticChannelCount > 0) {// 调整通道布局(分离音频和触觉数据)adjust_channels_non_destructive(buffer, mChannelCount, buffer,mChannelCount + mHapticChannelCount,audio_bytes_per_sample(format),audio_bytes_per_frame(mChannelCount, format) * mNormalFrameCount);}}// 更新剩余字节数(准备写入)mBytesRemaining = mCurrentWriteLength;// 如果设备处于挂起状态(如蓝牙通话)if (isSuspended()) {// 计算休眠时间(模拟写入完成)mSleepTimeUs = suspendSleepTimeUs();// 计算剩余帧数const size_t framesRemaining = mBytesRemaining / mFrameSize;// 更新统计信息mBytesWritten += mBytesRemaining;mFramesWritten += framesRemaining;mSuspendedFrames += framesRemaining; // 用于调整内核位置mBytesRemaining = 0; // 重置剩余字节}
}