diff --git a/services/audio_service/BUILD.gn b/services/audio_service/BUILD.gn index 01492aba75d4b941e5fa5b08619139614ee837fb..b47199482ab558595ad895bb9b7fd51cd0b46beb 100644 --- a/services/audio_service/BUILD.gn +++ b/services/audio_service/BUILD.gn @@ -47,6 +47,7 @@ ohos_shared_library("audio_common") { "common/src/audio_resample.cpp", "common/src/audio_ring_cache.cpp", "common/src/audio_thread_task.cpp", + "common/src/futex_tool.cpp", "common/src/linear_pos_time_model.cpp", "common/src/oh_audio_buffer.cpp", "common/src/volume_tools.cpp", diff --git a/services/audio_service/client/include/renderer_in_client_private.h b/services/audio_service/client/include/renderer_in_client_private.h index e963270d05422d8f1a63a5bc48ceca8333f1d75f..60721b9c92701ea06ba7706981b67b6910e43a37 100644 --- a/services/audio_service/client/include/renderer_in_client_private.h +++ b/services/audio_service/client/include/renderer_in_client_private.h @@ -334,7 +334,6 @@ private: bool silentModeAndMixWithOthers_ = false; uint64_t clientWrittenBytes_ = 0; - uint32_t underrunCount_ = 0; // ipc stream related AudioProcessConfig clientConfig_; sptr listener_ = nullptr; diff --git a/services/audio_service/client/src/renderer_in_client.cpp b/services/audio_service/client/src/renderer_in_client.cpp index 68c2553c81b3dd50e3899a6e28a50fde7114a5ef..62eb0c216e14bd5f54acf0fa6f23fc23201a90e4 100644 --- a/services/audio_service/client/src/renderer_in_client.cpp +++ b/services/audio_service/client/src/renderer_in_client.cpp @@ -50,6 +50,7 @@ #include "audio_stream_tracker.h" #include "audio_system_manager.h" #include "audio_utils.h" +#include "futex_tool.h" #include "ipc_stream_listener_impl.h" #include "ipc_stream_listener_stub.h" #include "volume_ramp.h" @@ -132,28 +133,9 @@ int32_t RendererInClientInner::OnOperationHandled(Operation operation, int64_t r rendererInfo_.pipeType = offloadEnable_ ? PIPE_TYPE_OFFLOAD : PIPE_TYPE_NORMAL_OUT; return SUCCESS; } - // read/write operation may print many log, use debug. - if (operation == UPDATE_STREAM) { - AUDIO_DEBUG_LOG("OnOperationHandled() UPDATE_STREAM result:%{public}" PRId64".", result); - // notify write if blocked - writeDataCV_.notify_all(); - return SUCCESS; - } - bool logFlag = true; - if (operation == UNDERFLOW_COUNT_ADD) { - logFlag = false; - if (!offloadEnable_) { - underrunCount_++; - } - AUDIO_DEBUG_LOG("recv underrun %{public}d", underrunCount_); - // in plan next: do more to reduce underrun - writeDataCV_.notify_all(); - return SUCCESS; - } - if (logFlag) { - AUDIO_INFO_LOG("OnOperationHandled() recv operation:%{public}d result:%{public}" PRId64".", operation, result); - } + AUDIO_INFO_LOG("OnOperationHandled() recv operation:%{public}d result:%{public}" PRId64".", operation, result); + std::unique_lock lock(callServerMutex_); notifiedOperation_ = operation; notifiedResult_ = result; @@ -1282,7 +1264,7 @@ bool RendererInClientInner::PauseAudioStream(StateChangeCmdType cmdType) std::lock_guard lock(writeDataMutex_); state_ = PAUSED; } - writeDataCV_.notify_all(); + FutexTool::FutexWake(clientBuffer_->GetFutex()); statusLock.unlock(); // in plan: call HiSysEventWrite @@ -1343,7 +1325,7 @@ bool RendererInClientInner::StopAudioStream() std::lock_guard lock(writeDataMutex_); state_ = STOPPED; } - writeDataCV_.notify_all(); + FutexTool::FutexWake(clientBuffer_->GetFutex()); statusLock.unlock(); // in plan: call HiSysEventWrite @@ -1391,7 +1373,7 @@ bool RendererInClientInner::ReleaseAudioStream(bool releaseRunner) cbBufferQueue_.PushNoWait({nullptr, 0, 0}); } cbThreadCv_.notify_all(); - writeDataCV_.notify_all(); + FutexTool::FutexWake(clientBuffer_->GetFutex(), IS_PRE_EXIT); if (callbackLoop_.joinable()) { callbackLoop_.join(); } @@ -1743,8 +1725,7 @@ void RendererInClientInner::ReportDataToResSched() int32_t RendererInClientInner::WriteCacheData(bool isDrain) { - std::string str = isDrain ? "RendererInClientInner::DrainCacheData" : "RendererInClientInner::WriteCacheData"; - Trace traceCache(str); + Trace traceCache(isDrain ? "RendererInClientInner::DrainCacheData" : "RendererInClientInner::WriteCacheData"); OptResult result = ringCache_->GetReadableSize(); CHECK_AND_RETURN_RET_LOG(result.ret == OPERATION_SUCCESS, ERR_OPERATION_FAILED, "ring cache unreadable"); @@ -1758,24 +1739,31 @@ int32_t RendererInClientInner::WriteCacheData(bool isDrain) int32_t sizeInFrame = clientBuffer_->GetAvailableDataFrames(); CHECK_AND_RETURN_RET_LOG(sizeInFrame >= 0, ERROR, "GetAvailableDataFrames invalid, %{public}d", sizeInFrame); - int32_t timeout = offloadEnable_ ? OFFLOAD_OPERATION_TIMEOUT_IN_MS : WRITE_CACHE_TIMEOUT_IN_MS; - std::unique_lock lock(writeDataMutex_); - bool stopWaiting = writeDataCV_.wait_for(lock, std::chrono::milliseconds(timeout), [this, &sizeInFrame] { + int32_t tryCount = 3; // try futex wait for 3 times. + FutexCode futexRes = FUTEX_OPERATION_FAILED; + while (static_cast(sizeInFrame) < spanSizeInFrame_ && tryCount-- > 0) { + int32_t timeout = offloadEnable_ ? OFFLOAD_OPERATION_TIMEOUT_IN_MS : WRITE_CACHE_TIMEOUT_IN_MS; + futexRes = FutexTool::FutexWait(clientBuffer_->GetFutex(), static_cast(timeout) * + AUDIO_US_PER_SECOND); + CHECK_AND_RETURN_RET_LOG(state_ == RUNNING, ERR_ILLEGAL_STATE, "failed with state:%{public}d", state_.load()); + if (futexRes == FUTEX_TIMEOUT) { + AUDIO_WARNING_LOG("write data time out, mode is %{public}s", (offloadEnable_ ? "offload" : "normal")); + return ERROR; + } sizeInFrame = clientBuffer_->GetAvailableDataFrames(); - return (state_ != RUNNING) || (sizeInFrame >= 0 && static_cast(sizeInFrame) >= spanSizeInFrame_); - }); - CHECK_AND_RETURN_RET_LOG(state_ == RUNNING, ERR_ILLEGAL_STATE, "Write while state is not running"); - CHECK_AND_RETURN_RET_LOG(stopWaiting == true, ERROR, "write data time out, mode is %{public}s", - (offloadEnable_ ? "offload" : "normal")); + if (futexRes == FUTEX_SUCCESS) { break; } + } + if (sizeInFrame < 0 || static_cast(clientBuffer_->GetAvailableDataFrames()) < spanSizeInFrame_) { + AUDIO_ERR_LOG("failed: sizeInFrame is:%{public}d, futexRes:%{public}d", sizeInFrame, futexRes); + return ERROR; + } BufferDesc desc = {}; uint64_t curWriteIndex = clientBuffer_->GetCurWriteFrame(); int32_t ret = clientBuffer_->GetWriteBuffer(curWriteIndex, desc); CHECK_AND_RETURN_RET_LOG(ret == SUCCESS, ERROR, "GetWriteBuffer failed %{public}d", ret); result = ringCache_->Dequeue({desc.buffer, targetSize}); CHECK_AND_RETURN_RET_LOG(result.ret == OPERATION_SUCCESS, ERROR, "ringCache Dequeue failed %{public}d", result.ret); - AUDIO_DEBUG_LOG("RendererInClientInner::WriteCacheData() curWriteIndex[%{public}" PRIu64 "], spanSizeInFrame_ " - "%{public}u", curWriteIndex, spanSizeInFrame_); // volume process in client if (volumeRamp_.IsActive()) { @@ -1914,7 +1902,9 @@ int32_t RendererInClientInner::Read(uint8_t &buffer, size_t userSize, bool isBlo uint32_t RendererInClientInner::GetUnderflowCount() { - return underrunCount_; + CHECK_AND_RETURN_RET_LOG(clientBuffer_ != nullptr, 0, "buffer is not inited"); + + return clientBuffer_->GetUnderrunCount(); } uint32_t RendererInClientInner::GetOverflowCount() @@ -1925,7 +1915,8 @@ uint32_t RendererInClientInner::GetOverflowCount() void RendererInClientInner::SetUnderflowCount(uint32_t underflowCount) { - underrunCount_ = underflowCount; + CHECK_AND_RETURN_LOG(clientBuffer_ != nullptr, "buffer is not inited"); + clientBuffer_->SetUnderrunCount(underflowCount); } void RendererInClientInner::SetOverflowCount(uint32_t overflowCount) @@ -2079,7 +2070,7 @@ void RendererInClientInner::GetSwitchInfo(IAudioStream::SwitchInfo& info) void RendererInClientInner::GetStreamSwitchInfo(IAudioStream::SwitchInfo& info) { info.cachePath = cachePath_; - info.underFlowCount = underrunCount_; + info.underFlowCount = GetUnderflowCount(); info.effectMode = effectMode_; info.renderRate = rendererRate_; info.clientPid = clientPid_; diff --git a/services/audio_service/common/include/futex_tool.h b/services/audio_service/common/include/futex_tool.h new file mode 100644 index 0000000000000000000000000000000000000000..6c388383286664cd6ad56c4d8c0d30c6c98e9834 --- /dev/null +++ b/services/audio_service/common/include/futex_tool.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef FUTEX_TOOL_H +#define FUTEX_TOOL_H + +#include +#include + +namespace OHOS { +namespace AudioStandard { +namespace { +const uint32_t IS_READY = 0; +const uint32_t IS_NOT_READY = 1; +const uint32_t IS_PRE_EXIT = 2; +} +enum FutexCode : int32_t { + FUTEX_SUCCESS = 0, + FUTEX_TIMEOUT, + FUTEX_INVALID_PARAMS, + FUTEX_OPERATION_FAILED, + FUTEX_PRE_EXIT, +}; +class FutexTool { +public: + /** + * FutexWait will first try change futexPtr from IS_READY to IS_NOT_READY, then acomicly wait on IS_NOT_READY. + * After Waked up, will check futexPtr == IS_NOT_READY + */ + static FutexCode FutexWait(std::atomic *futexPtr, int64_t timeout); + static FutexCode FutexWake(std::atomic *futexPtr, uint32_t wakeVal = IS_READY); +}; +} // namespace AudioStandard +} // namespace OHOS +#endif // FUTEX_TOOL_H diff --git a/services/audio_service/common/include/oh_audio_buffer.h b/services/audio_service/common/include/oh_audio_buffer.h index 97654d93a0a53f5145b30bf1566ef420ab5a6695..02690a26eed754627e5261c285cdb028921e701c 100644 --- a/services/audio_service/common/include/oh_audio_buffer.h +++ b/services/audio_service/common/include/oh_audio_buffer.h @@ -63,6 +63,8 @@ struct BasicBufferInfo { uint32_t spanSizeInFrame; uint32_t byteSizePerFrame; + std::atomic futexObj; + std::atomic streamStatus; // basic read/write postion @@ -70,6 +72,8 @@ struct BasicBufferInfo { std::atomic curWriteFrame; std::atomic curReadFrame; + std::atomic underrunCount; + std::atomic handlePos; std::atomic handleTime; @@ -126,6 +130,10 @@ public: std::atomic *GetStreamStatus(); + uint32_t GetUnderrunCount(); + + bool SetUnderrunCount(uint32_t count); + bool GetHandleInfo(uint64_t &frames, int64_t &nanoTime); void SetHandleInfo(uint64_t frames, int64_t nanoTime); @@ -157,6 +165,7 @@ public: uint32_t GetSpanCount(); + std::atomic *GetFutex(); uint8_t *GetDataBase(); size_t GetDataSize(); private: diff --git a/services/audio_service/common/src/futex_tool.cpp b/services/audio_service/common/src/futex_tool.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e223fa4832ba8e37f6604e773328d6b1665a8864 --- /dev/null +++ b/services/audio_service/common/src/futex_tool.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#undef LOG_TAG +#define LOG_TAG "FutexTool" + +#include "futex_tool.h" + +#include +#include "linux/futex.h" +#include + +#include "audio_errors.h" +#include "audio_log.h" +#include "audio_utils.h" + +namespace OHOS { +namespace AudioStandard { +namespace { +const int32_t WAIT_TRY_COUNT = 10; +const int64_t SEC_TO_NANOSEC = 1000000000; +} +void TimeoutToRealtime(int64_t timeout, struct timespec &realtime) +{ + clock_gettime(CLOCK_MONOTONIC, &realtime); + int64_t timeoutNanoSec = timeout % SEC_TO_NANOSEC; + int64_t timeoutSec = timeout / SEC_TO_NANOSEC; + + if (timeoutNanoSec + realtime.tv_nsec >= SEC_TO_NANOSEC) { + realtime.tv_nsec = (timeoutNanoSec + realtime.tv_nsec) - SEC_TO_NANOSEC; + realtime.tv_sec += timeoutSec + 1; + } else { + realtime.tv_nsec += timeoutNanoSec; + realtime.tv_sec += timeoutSec; + } +} + +FutexCode FutexTool::FutexWait(std::atomic *futexPtr, int64_t timeout) +{ + CHECK_AND_RETURN_RET_LOG(futexPtr != nullptr, FUTEX_INVALID_PARAMS, "futexPtr is null"); + Trace trace("FutexTool::FutexWait"); + uint32_t current = futexPtr->load(); + if (current != IS_READY && current != IS_NOT_READY && current != IS_PRE_EXIT) { + AUDIO_ERR_LOG("failed: invalid param:%{public}u", current); + return FUTEX_INVALID_PARAMS; + } + struct timespec waitTime; + if (timeout > 0) { + TimeoutToRealtime(timeout, waitTime); + } + + uint32_t expect = IS_READY; + if (!futexPtr->compare_exchange_strong(expect, IS_NOT_READY)) { + AUDIO_ERR_LOG("failed with invalid status:%{public}u", expect); + return FUTEX_INVALID_PARAMS; + } + long res = 0; + int32_t tryCount = 0; + while (tryCount < WAIT_TRY_COUNT) { + if (futexPtr->load() == IS_PRE_EXIT) { + AUDIO_INFO_LOG("pre_exit is called!"); + return FUTEX_PRE_EXIT; + } + res = syscall(__NR_futex, futexPtr, FUTEX_WAIT, IS_NOT_READY, (timeout <= 0 ? NULL : &waitTime), NULL, 0); + if (res == 0 && futexPtr->load() == IS_READY) { + return FUTEX_SUCCESS; // return success here + } + if (errno == ETIMEDOUT) { + AUDIO_WARNING_LOG("result:%{public}ld, timeout errno[%{public}d]:%{public}s", res, errno, strerror(errno)); + return FUTEX_TIMEOUT; + } + if (errno != EAGAIN) { + AUDIO_WARNING_LOG("result:%{public}ld, errno[%{public}d]:%{public}s", res, errno, strerror(errno)); + return FUTEX_OPERATION_FAILED; + } + tryCount++; + } + if (tryCount >= WAIT_TRY_COUNT) { + AUDIO_ERR_LOG("too much spurious wake-up"); + } + return FUTEX_OPERATION_FAILED; +} + +FutexCode FutexTool::FutexWake(std::atomic *futexPtr, uint32_t wakeVal) +{ + CHECK_AND_RETURN_RET_LOG(futexPtr != nullptr, FUTEX_INVALID_PARAMS, "futexPtr is null"); + Trace trace("FutexTool::FutexWake"); + uint32_t current = futexPtr->load(); + if (current != IS_READY && current != IS_NOT_READY && current != IS_PRE_EXIT) { + AUDIO_ERR_LOG("failed: invalid param:%{public}u", current); + return FUTEX_INVALID_PARAMS; + } + if (wakeVal == IS_PRE_EXIT) { + futexPtr->store(IS_PRE_EXIT); + syscall(__NR_futex, futexPtr, FUTEX_WAKE, INT_MAX, NULL, NULL, 0); + return FUTEX_SUCCESS; + } + uint32_t expect = IS_NOT_READY; + if (futexPtr->compare_exchange_strong(expect, IS_READY)) { + long res = syscall(__NR_futex, futexPtr, FUTEX_WAKE, INT_MAX, NULL, NULL, 0); + if (res < 0) { + AUDIO_ERR_LOG("failed:%{public}ld, errno[%{public}d]:%{public}s", res, errno, strerror(errno)); + return FUTEX_OPERATION_FAILED; + } + } + return FUTEX_SUCCESS; +} +} // namespace AudioStandard +} // namespace OHOS diff --git a/services/audio_service/common/src/oh_audio_buffer.cpp b/services/audio_service/common/src/oh_audio_buffer.cpp index 43f76cbbbed293162ee53282c402773ef23573db..c4ca1c5096c69ee551382a6374056605eff3bb80 100644 --- a/services/audio_service/common/src/oh_audio_buffer.cpp +++ b/services/audio_service/common/src/oh_audio_buffer.cpp @@ -25,6 +25,7 @@ #include "audio_errors.h" #include "audio_log.h" +#include "futex_tool.h" namespace OHOS { namespace AudioStandard { @@ -268,6 +269,8 @@ int32_t OHAudioBuffer::Init(int dataFd, int infoFd) basicBufferInfo_->curReadFrame.store(0); basicBufferInfo_->curWriteFrame.store(0); + basicBufferInfo_->underrunCount.store(0); + basicBufferInfo_->streamVolume.store(MAX_FLOAT_VOLUME); basicBufferInfo_->duckFactor.store(MAX_FLOAT_VOLUME); @@ -452,6 +455,22 @@ bool OHAudioBuffer::SetDuckFactor(float duckFactor) return true; } + +uint32_t OHAudioBuffer::GetUnderrunCount() +{ + CHECK_AND_RETURN_RET_LOG(basicBufferInfo_ != nullptr, 0, + "Get nullptr, buffer is not inited."); + return basicBufferInfo_->underrunCount.load(); +} + +bool OHAudioBuffer::SetUnderrunCount(uint32_t count) +{ + CHECK_AND_RETURN_RET_LOG(basicBufferInfo_ != nullptr, false, + "Get nullptr, buffer is not inited."); + basicBufferInfo_->underrunCount.store(count); + return true; +} + bool OHAudioBuffer::GetHandleInfo(uint64_t &frames, int64_t &nanoTime) { CHECK_AND_RETURN_RET_LOG(basicBufferInfo_ != nullptr, false, @@ -642,6 +661,11 @@ uint32_t OHAudioBuffer::GetSpanCount() return spanConut_; } +std::atomic *OHAudioBuffer::GetFutex() +{ + return &basicBufferInfo_->futexObj; +} + uint8_t *OHAudioBuffer::GetDataBase() { return dataBase_; diff --git a/services/audio_service/server/include/renderer_in_server.h b/services/audio_service/server/include/renderer_in_server.h index 7fe0359929d7238a1d3bfeb179ce3384f6dd499e..a592b161a967f778f97748d2ccbb6a0117302b8f 100644 --- a/services/audio_service/server/include/renderer_in_server.h +++ b/services/audio_service/server/include/renderer_in_server.h @@ -142,6 +142,7 @@ private: bool isNeedFade_ = false; float oldAppliedVolume_ = MAX_FLOAT_VOLUME; std::mutex updateIndexLock_; + uint32_t underrunCount_ = 0; bool resetTime_ = false; uint64_t resetTimestamp_ = 0; std::mutex writeLock_; diff --git a/services/audio_service/server/src/renderer_in_server.cpp b/services/audio_service/server/src/renderer_in_server.cpp index 0a8183d6124c3d1ed7acba8994e64c9566e119a6..0c1fef50e600470bfa5f11a29d2fa420f26d3bbc 100644 --- a/services/audio_service/server/src/renderer_in_server.cpp +++ b/services/audio_service/server/src/renderer_in_server.cpp @@ -22,6 +22,7 @@ #include "audio_log.h" #include "audio_utils.h" #include "audio_service.h" +#include "futex_tool.h" #include "i_stream_manager.h" #ifdef RESSCHE_ENABLE #include "res_type.h" @@ -228,7 +229,8 @@ void RendererInServer::OnStatusUpdateSub(IOperation operation) } break; case OPERATION_UNDERFLOW: - stateListener->OnOperationHandled(UNDERFLOW_COUNT_ADD, 0); + underrunCount_++; + audioServerBuffer_->SetUnderrunCount(underrunCount_); break; case OPERATION_SET_OFFLOAD_ENABLE: case OPERATION_UNSET_OFFLOAD_ENABLE: @@ -495,7 +497,7 @@ int32_t RendererInServer::WriteData() Trace trace2("RendererInServer::Underrun"); std::shared_ptr stateListener = streamListener_.lock(); CHECK_AND_RETURN_RET_LOG(stateListener != nullptr, ERR_OPERATION_FAILED, "IStreamListener is nullptr"); - stateListener->OnOperationHandled(UPDATE_STREAM, currentReadFrame); + FutexTool::FutexWake(audioServerBuffer_->GetFutex()); return ERR_OPERATION_FAILED; } @@ -526,7 +528,7 @@ int32_t RendererInServer::WriteData() } std::shared_ptr stateListener = streamListener_.lock(); CHECK_AND_RETURN_RET_LOG(stateListener != nullptr, SUCCESS, "IStreamListener is nullptr"); - stateListener->OnOperationHandled(UPDATE_STREAM, currentReadFrame); + FutexTool::FutexWake(audioServerBuffer_->GetFutex()); return SUCCESS; }