一、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; // 重置剩余字節}
}