1 /*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #ifndef LOG_TAG
17 #define LOG_TAG "HpaeSoftLink"
18 #endif
19
20 #include "hpae_soft_link.h"
21 #ifdef ENABLE_HOOK_PCM
22 #include <thread>
23 #endif
24 #include "audio_errors.h"
25 #include "audio_utils.h"
26 #include "audio_volume.h"
27 #include "i_hpae_manager.h"
28 #include "audio_engine_log.h"
29
30 namespace OHOS {
31 namespace AudioStandard {
32 namespace HPAE {
33 static constexpr uint32_t FIRST_SESSIONID = 90000;
34 static constexpr uint32_t MAX_VALID_SESSIONID = 99999;
35 static constexpr uint32_t OPERATION_TIMEOUT_IN_MS = 1000; // 1000ms
36 static constexpr uint32_t DEFAULT_FRAME_LEN_MS = 20;
37 static constexpr uint32_t MS_PER_SECOND = 1000;
38 static constexpr uint32_t DEFAULT_RING_BUFFER_NUM = 4;
39 static constexpr int32_t MAX_OVERFLOW_UNDERRUN_COUNT = 50; // 1s
40 uint32_t HpaeSoftLink::g_sessionId = FIRST_SESSIONID; // begin at 90000
CreateSoftLink(uint32_t sinkIdx,uint32_t sourceIdx,SoftLinkMode mode)41 std::shared_ptr<IHpaeSoftLink> IHpaeSoftLink::CreateSoftLink(uint32_t sinkIdx, uint32_t sourceIdx, SoftLinkMode mode)
42 {
43 std::shared_ptr<IHpaeSoftLink> softLink = std::make_shared<HpaeSoftLink>(sinkIdx, sourceIdx, mode);
44 CHECK_AND_RETURN_RET_LOG(softLink != nullptr, nullptr, "new HpaeSoftLink failed");
45 CHECK_AND_RETURN_RET_LOG(softLink->Init() == SUCCESS, nullptr, "HpaeSoftLink init failed");
46 return softLink;
47 }
48
GenerateSessionId()49 uint32_t HpaeSoftLink::GenerateSessionId()
50 {
51 std::lock_guard<std::mutex> lock(sessionIdMutex_);
52 uint32_t sessionId = g_sessionId++;
53 AUDIO_INFO_LOG("hpae softlink sessionId: %{public}u", sessionId);
54 if (g_sessionId > MAX_VALID_SESSIONID) {
55 AUDIO_WARNING_LOG("sessionId is too large, reset it!");
56 g_sessionId = FIRST_SESSIONID;
57 }
58 return sessionId;
59 }
60
HpaeSoftLink(uint32_t sinkIdx,uint32_t sourceIdx,SoftLinkMode mode)61 HpaeSoftLink::HpaeSoftLink(uint32_t sinkIdx, uint32_t sourceIdx, SoftLinkMode mode)
62 : sinkIdx_(sinkIdx), sourceIdx_(sourceIdx), linkMode_(mode), state_(HpaeSoftLinkState::NEW)
63 {
64 sinkInfo_.sinkId = sinkIdx;
65 sourceInfo_.sourceId = sourceIdx;
66 }
67
~HpaeSoftLink()68 HpaeSoftLink::~HpaeSoftLink()
69 {
70 AUDIO_INFO_LOG("~HpaeSoftLink");
71 Release();
72 }
73
Init()74 int32_t HpaeSoftLink::Init()
75 {
76 Trace trace("HpaeSoftLink::Init");
77 AUDIO_INFO_LOG("init in");
78 CHECK_AND_RETURN_RET_LOG(state_ != HpaeSoftLinkState::PREPARED, SUCCESS, "softlink already inited");
79 CHECK_AND_RETURN_RET_LOG(state_ == HpaeSoftLinkState::NEW, ERR_ILLEGAL_STATE, "init error state");
80 CHECK_AND_RETURN_RET_LOG(sinkIdx_ != HDI_INVALID_ID && sourceIdx_ != HDI_INVALID_ID, ERR_INVALID_PARAM,
81 "invalid sinkIdx or capturerIdx");
82
83 int32_t ret = GetDeviceInfo();
84 CHECK_AND_RETURN_RET(ret == SUCCESS, ERR_OPERATION_FAILED);
85
86 size_t frameBytes = sinkInfo_.channels * static_cast<size_t>(GetSizeFromFormat(sinkInfo_.format)) *
87 DEFAULT_FRAME_LEN_MS * sinkInfo_.samplingRate / MS_PER_SECOND;
88 size_t size = DEFAULT_RING_BUFFER_NUM * frameBytes;
89 bufferQueue_ = AudioRingCache::Create(size);
90 CHECK_AND_RETURN_RET_LOG(bufferQueue_ != nullptr, ERR_OPERATION_FAILED, "bufferQueue create error");
91 tempBuffer_.resize(frameBytes);
92 ret = CreateStream();
93 if (ret == SUCCESS) {
94 state_ = HpaeSoftLinkState::PREPARED;
95 }
96 return ret;
97 }
98
GetDeviceInfo()99 int32_t HpaeSoftLink::GetDeviceInfo()
100 {
101 Trace trace("HpaeSoftLink::GetDeviceInfo");
102 AUDIO_INFO_LOG("get device info");
103 std::unique_lock<std::mutex> lock(callbackMutex_);
104 isDeviceOperationFinish_ = 0;
105 int32_t retGetSink = ERROR;
106 IHpaeManager::GetHpaeManager().GetSinkInfoByIdx(sinkIdx_,
107 [this, &retGetSink](const HpaeSinkInfo &sinkInfo, int32_t result) {
108 sinkInfo_ = sinkInfo;
109 retGetSink = result;
110 OnDeviceInfoReceived(SOFTLINK_SINK_OPERATION);
111 });
112 int32_t retGetSource = ERROR;
113 IHpaeManager::GetHpaeManager().GetSourceInfoByIdx(sourceIdx_,
114 [this, &retGetSource](const HpaeSourceInfo &sourceInfo, int32_t result) {
115 sourceInfo_ = sourceInfo;
116 retGetSource = result;
117 OnDeviceInfoReceived(SOFTLINK_SOURCE_OPERATION);
118 });
119 bool stopWaiting = callbackCV_.wait_for(lock, std::chrono::milliseconds(OPERATION_TIMEOUT_IN_MS), [this] {
120 return (isDeviceOperationFinish_ & SOFTLINK_SINK_OPERATION) &&
121 (isDeviceOperationFinish_ & SOFTLINK_SOURCE_OPERATION);
122 });
123 CHECK_AND_RETURN_RET_LOG(stopWaiting, ERROR, "get device info timeout");
124 CHECK_AND_RETURN_RET_LOG(retGetSink == SUCCESS, ERROR, "get sink info failed");
125 CHECK_AND_RETURN_RET_LOG(retGetSource == SUCCESS, ERROR, "get source info failed");
126 return SUCCESS;
127 }
128
OnDeviceInfoReceived(const HpaeSoftLinkDeviceOperation & operation)129 void HpaeSoftLink::OnDeviceInfoReceived(const HpaeSoftLinkDeviceOperation &operation)
130 {
131 std::unique_lock<std::mutex> lock(callbackMutex_);
132 isDeviceOperationFinish_ |= operation;
133 AUDIO_INFO_LOG("GetDeviceInfo callback, operation: %{public}d", operation);
134 callbackCV_.notify_all();
135 }
136
TransSinkInfoToStreamInfo(HpaeStreamInfo & info,const HpaeStreamClassType & streamClassType)137 void HpaeSoftLink::TransSinkInfoToStreamInfo(HpaeStreamInfo &info, const HpaeStreamClassType &streamClassType)
138 {
139 info.channels = sinkInfo_.channels;
140 info.samplingRate = sinkInfo_.samplingRate;
141 info.format = sinkInfo_.format;
142 info.channelLayout = sinkInfo_.channelLayout;
143 info.frameLen = DEFAULT_FRAME_LEN_MS * static_cast<uint32_t>(sinkInfo_.samplingRate) / MS_PER_SECOND;
144 info.streamClassType = streamClassType;
145 info.isMoveAble = false;
146 info.sessionId = GenerateSessionId();
147 if (streamClassType == HPAE_STREAM_CLASS_TYPE_PLAY) {
148 info.streamType = STREAM_VOICE_CALL;
149 info.deviceName = sinkInfo_.deviceName;
150 info.sourceType = SOURCE_TYPE_INVALID;
151 info.fadeType = DEFAULT_FADE;
152 info.effectInfo.effectMode = EFFECT_DEFAULT;
153 info.effectInfo.effectScene = SCENE_VOIP_DOWN;
154 info.effectInfo.systemVolumeType = STREAM_VOICE_CALL;
155 info.effectInfo.streamUsage = STREAM_USAGE_VOICE_COMMUNICATION;
156 } else {
157 info.streamType = STREAM_SOURCE_VOICE_CALL;
158 info.deviceName = sourceInfo_.deviceName;
159 info.sourceType = SOURCE_TYPE_MIC;
160 }
161 }
162
CreateStream()163 int32_t HpaeSoftLink::CreateStream()
164 {
165 TransSinkInfoToStreamInfo(rendererStreamInfo_, HPAE_STREAM_CLASS_TYPE_PLAY);
166 TransSinkInfoToStreamInfo(capturerStreamInfo_, HPAE_STREAM_CLASS_TYPE_RECORD);
167
168 uint32_t &rendererSessionId = rendererStreamInfo_.sessionId;
169 IHpaeManager::GetHpaeManager().CreateStream(rendererStreamInfo_);
170 IHpaeManager::GetHpaeManager().RegisterStatusCallback(HPAE_STREAM_CLASS_TYPE_PLAY, rendererSessionId,
171 shared_from_this());
172 IHpaeManager::GetHpaeManager().RegisterWriteCallback(rendererSessionId, shared_from_this());
173 StreamVolumeParams streamVolumeParams = {
174 rendererSessionId, rendererStreamInfo_.streamType,
175 rendererStreamInfo_.effectInfo.streamUsage, -1, -1,
176 false, AUDIOSTREAM_VOLUMEMODE_SYSTEM_GLOBAL, false
177 };
178 AudioVolume::GetInstance()->AddStreamVolume(streamVolumeParams);
179 streamStateMap_[rendererStreamInfo_.sessionId] = HpaeSoftLinkState::PREPARED;
180
181 uint32_t &capturerSessionId = capturerStreamInfo_.sessionId;
182 IHpaeManager::GetHpaeManager().CreateStream(capturerStreamInfo_);
183 IHpaeManager::GetHpaeManager().RegisterStatusCallback(HPAE_STREAM_CLASS_TYPE_RECORD, capturerSessionId,
184 shared_from_this());
185 IHpaeManager::GetHpaeManager().RegisterReadCallback(capturerSessionId, shared_from_this());
186 streamStateMap_[capturerStreamInfo_.sessionId] = HpaeSoftLinkState::PREPARED;
187 return SUCCESS;
188 }
189
SetVolume(float volume)190 int32_t HpaeSoftLink::SetVolume(float volume)
191 {
192 CHECK_AND_RETURN_RET_LOG(state_ != HpaeSoftLinkState::NEW, ERR_ILLEGAL_STATE, "softlink not prepared");
193 CHECK_AND_RETURN_RET_LOG(state_ != HpaeSoftLinkState::RELEASED, ERR_ILLEGAL_STATE, "softlink already release");
194 AudioVolume::GetInstance()->SetStreamVolume(rendererStreamInfo_.sessionId, volume);
195 return SUCCESS;
196 }
197
Start()198 int32_t HpaeSoftLink::Start()
199 {
200 Trace trace("HpaeSoftLink::Start");
201 AUDIO_INFO_LOG("Start in");
202 CHECK_AND_RETURN_RET_LOG(state_ != HpaeSoftLinkState::RUNNING, SUCCESS, "softlink already start");
203 CHECK_AND_RETURN_RET_LOG(state_ == HpaeSoftLinkState::PREPARED || state_ == HpaeSoftLinkState::STOPPED,
204 ERR_ILLEGAL_STATE, "softlink not init");
205 std::unique_lock<std::mutex> lock(callbackMutex_);
206 isStreamOperationFinish_ = 0;
207 IHpaeManager::GetHpaeManager().Start(HPAE_STREAM_CLASS_TYPE_PLAY, rendererStreamInfo_.sessionId);
208 IHpaeManager::GetHpaeManager().Start(HPAE_STREAM_CLASS_TYPE_RECORD, capturerStreamInfo_.sessionId);
209 bool stopWaiting = callbackCV_.wait_for(lock, std::chrono::milliseconds(OPERATION_TIMEOUT_IN_MS), [this] {
210 return (isStreamOperationFinish_ & SOFTLINK_RENDERER_OPERATION) &&
211 (isStreamOperationFinish_ & SOFTLINK_CAPTURER_OPERATION);
212 });
213
214 CHECK_AND_RETURN_RET_LOG(stopWaiting, ERROR, "start timeout");
215 {
216 std::lock_guard<std::mutex> stateLock(stateMutex_);
217 CHECK_AND_RETURN_RET_LOG(streamStateMap_[rendererStreamInfo_.sessionId] == HpaeSoftLinkState::RUNNING,
218 ERROR, "start renderer[%{public}d] failed", rendererStreamInfo_.sessionId);
219 CHECK_AND_RETURN_RET_LOG(streamStateMap_[capturerStreamInfo_.sessionId] == HpaeSoftLinkState::RUNNING,
220 ERROR, "start capturer[%{public}d] failed", capturerStreamInfo_.sessionId);
221 state_ = HpaeSoftLinkState::RUNNING;
222 }
223 return SUCCESS;
224 }
225
Stop()226 int32_t HpaeSoftLink::Stop()
227 {
228 Trace trace("HpaeSoftLink::Stop");
229 CHECK_AND_RETURN_RET_LOG(state_ != HpaeSoftLinkState::STOPPED, SUCCESS, "softlink already stop");
230 CHECK_AND_RETURN_RET_LOG(state_ == HpaeSoftLinkState::RUNNING, ERR_ILLEGAL_STATE, "softlink not init");
231 IHpaeManager::GetHpaeManager().Stop(HPAE_STREAM_CLASS_TYPE_PLAY, rendererStreamInfo_.sessionId);
232 IHpaeManager::GetHpaeManager().Stop(HPAE_STREAM_CLASS_TYPE_RECORD, capturerStreamInfo_.sessionId);
233 std::lock_guard<std::mutex> lock(stateMutex_);
234 state_ = HpaeSoftLinkState::STOPPED;
235 return SUCCESS;
236 }
237
Release()238 int32_t HpaeSoftLink::Release()
239 {
240 Trace trace("HpaeSoftLink::Release");
241 CHECK_AND_RETURN_RET_LOG(state_ != HpaeSoftLinkState::RELEASED, SUCCESS, "softlink already release");
242 if (state_ == HpaeSoftLinkState::RUNNING) {
243 AUDIO_INFO_LOG("softlink not stop, stop before release");
244 Stop();
245 }
246 IHpaeManager::GetHpaeManager().Release(HPAE_STREAM_CLASS_TYPE_PLAY, rendererStreamInfo_.sessionId);
247 AudioVolume::GetInstance()->RemoveStreamVolume(rendererStreamInfo_.sessionId);
248 IHpaeManager::GetHpaeManager().Release(HPAE_STREAM_CLASS_TYPE_RECORD, capturerStreamInfo_.sessionId);
249 std::lock_guard<std::mutex> lock(stateMutex_);
250 state_ = HpaeSoftLinkState::RELEASED;
251 return SUCCESS;
252 }
253
FlushRingCache()254 void HpaeSoftLink::FlushRingCache()
255 {
256 CHECK_AND_RETURN_LOG(bufferQueue_ != nullptr, "ring cache is null");
257 bufferQueue_->ResetBuffer();
258 }
259
OnStatusUpdate(IOperation operation,uint32_t streamIndex)260 void HpaeSoftLink::OnStatusUpdate(IOperation operation, uint32_t streamIndex)
261 {
262 AUDIO_INFO_LOG("stream %{public}u recv operation:%{public}d", streamIndex, operation);
263 CHECK_AND_RETURN_LOG(operation != OPERATION_RELEASED, "stream already released");
264 CHECK_AND_RETURN_LOG(streamIndex == rendererStreamInfo_.sessionId || streamIndex == capturerStreamInfo_.sessionId,
265 "invalid streamIndex");
266 {
267 std::lock_guard<std::mutex> lock(stateMutex_);
268 if (operation == OPERATION_STARTED) {
269 streamStateMap_[streamIndex] = HpaeSoftLinkState::RUNNING;
270 } else if (operation == OPERATION_STOPPED) {
271 streamStateMap_[streamIndex] = HpaeSoftLinkState::STOPPED;
272 if (streamIndex == capturerStreamInfo_.sessionId) {
273 FlushRingCache();
274 }
275 } else if (operation == OPERATION_RELEASED) {
276 streamStateMap_[streamIndex] = HpaeSoftLinkState::RELEASED;
277 } else {
278 return;
279 }
280 }
281 std::lock_guard<std::mutex> lock(callbackMutex_);
282 isStreamOperationFinish_ |=
283 (streamIndex == rendererStreamInfo_.sessionId ? SOFTLINK_RENDERER_OPERATION : SOFTLINK_CAPTURER_OPERATION);
284 callbackCV_.notify_all();
285 }
286
OnStreamData(AudioCallBackStreamInfo & callbackStreamInfo)287 int32_t HpaeSoftLink::OnStreamData(AudioCallBackStreamInfo& callbackStreamInfo)
288 {
289 Trace trace("HpaeSoftLink::OnStreamData, [" +std::to_string(rendererStreamInfo_.sessionId) + "]OnWriteData");
290 #ifdef ENABLE_HOOK_PCM
291 if (sinkInfo_.adapterName == "file_io") {
292 std::this_thread::sleep_for(std::chrono::milliseconds(20)); // 20s for file_io sleep
293 }
294 #endif
295 int8_t *inputData = callbackStreamInfo.inputData;
296 size_t requestDataLen = callbackStreamInfo.requestDataLen;
297 OptResult result = bufferQueue_->GetReadableSize();
298 CHECK_AND_RETURN_RET_LOG(result.ret == OPERATION_SUCCESS, ERROR,
299 "ringBuffer get readable invalid size: %{public}zu", result.size);
300 if (result.size == 0 || result.size < requestDataLen) {
301 ++underRunCount_;
302 AUDIO_INFO_LOG("underrun[%{public}d]!, readable size is invalid, result.size[%{public}zu],"
303 "requestDataLen[%{public}zu]", underRunCount_, result.size, requestDataLen);
304 } else {
305 underRunCount_ = 0;
306 }
307 if (underRunCount_ >= MAX_OVERFLOW_UNDERRUN_COUNT) {
308 IHpaeManager::GetHpaeManager().Stop(HPAE_STREAM_CLASS_TYPE_PLAY, rendererStreamInfo_.sessionId);
309 AUDIO_WARNING_LOG("renderer[%{public}u] will stop", rendererStreamInfo_.sessionId);
310 underRunCount_ = 0;
311 return ERROR;
312 }
313 AUDIO_DEBUG_LOG("readable size: %{public}zu, requestDataLen: %{public}zu", result.size, requestDataLen);
314 result = bufferQueue_->Dequeue({reinterpret_cast<uint8_t *>(inputData), requestDataLen});
315 CHECK_AND_RETURN_RET_LOG(result.ret == OPERATION_SUCCESS, ERROR, "ringBuffer dequeue failed");
316 return SUCCESS;
317 }
318
CopyRightToLeft(uint8_t * data,size_t size,const AudioSampleFormat & format)319 static void CopyRightToLeft(uint8_t *data, size_t size, const AudioSampleFormat &format)
320 {
321 CHECK_AND_RETURN_LOG(data != nullptr && size > 0, "error param");
322 const size_t bytesPerSample = static_cast<size_t>(GetSizeFromFormat(format));
323 const size_t frameSize = bytesPerSample * 2;
324 CHECK_AND_RETURN_LOG(size > frameSize, "invalid data size");
325 uint8_t *left = nullptr;
326 uint8_t *right = nullptr;
327 for (size_t i = 0; i < size - frameSize + 1; i += frameSize) {
328 left = data + i;
329 right = left + bytesPerSample;
330 CHECK_AND_RETURN_LOG(memcpy_s(left, bytesPerSample, right, bytesPerSample) == 0, "memcpy_s failed");
331 }
332 }
333
OnStreamData(AudioCallBackCapturerStreamInfo & callbackStreamInfo)334 int32_t HpaeSoftLink::OnStreamData(AudioCallBackCapturerStreamInfo& callbackStreamInfo)
335 {
336 Trace trace("HpaeSoftLink::OnStreamData, [" + std::to_string(capturerStreamInfo_.sessionId) + "]OnReadData");
337 #ifdef ENABLE_HOOK_PCM
338 if (sourceInfo_.adapterName == "file_io") {
339 std::this_thread::sleep_for(std::chrono::milliseconds(20)); // 20s for file_io sleep
340 }
341 #endif
342 int8_t *outputData = callbackStreamInfo.outputData;
343 size_t requestDataLen = callbackStreamInfo.requestDataLen;
344 // todo : channel select
345 if (linkMode_ == SoftLinkMode::HEARING_AID && sinkInfo_.channels == STEREO) {
346 CopyRightToLeft(reinterpret_cast<uint8_t *>(outputData), requestDataLen, sinkInfo_.format);
347 }
348 OptResult result = bufferQueue_->GetWritableSize();
349 CHECK_AND_RETURN_RET_LOG(result.ret == OPERATION_SUCCESS, ERR_READ_FAILED,
350 "ringBuffer get writeable invalid size: %{public}zu", result.size);
351 if (result.size == 0 || result.size < requestDataLen) {
352 ++overFlowCount_;
353 AUDIO_INFO_LOG("overflow[%{public}d]!, writable size is invalid, result.size[%{public}zu],"
354 "requestDataLen[%{public}zu]", overFlowCount_, result.size, requestDataLen);
355 bufferQueue_->Dequeue({reinterpret_cast<uint8_t *>(tempBuffer_.data()), requestDataLen});
356 } else {
357 overFlowCount_ = 0;
358 }
359 if (overFlowCount_ >= MAX_OVERFLOW_UNDERRUN_COUNT) {
360 IHpaeManager::GetHpaeManager().Stop(HPAE_STREAM_CLASS_TYPE_RECORD, capturerStreamInfo_.sessionId);
361 AUDIO_WARNING_LOG("capturer[%{public}u] will stop", capturerStreamInfo_.sessionId);
362 overFlowCount_ = 0;
363 return ERROR;
364 }
365 AUDIO_DEBUG_LOG("writable size: %{public}zu, requestDataLen: %{public}zu", result.size, requestDataLen);
366 result = bufferQueue_->Enqueue({reinterpret_cast<uint8_t *>(outputData), requestDataLen});
367 CHECK_AND_RETURN_RET_LOG(result.ret == OPERATION_SUCCESS, ERROR, "ringBuffer enqueue failed");
368 return SUCCESS;
369 }
370
371 // for test
GetStreamStateById(uint32_t sessionId)372 HpaeSoftLinkState HpaeSoftLink::GetStreamStateById(uint32_t sessionId)
373 {
374 std::lock_guard<std::mutex> lock(stateMutex_);
375 CHECK_AND_RETURN_RET_LOG(streamStateMap_.find(sessionId) != streamStateMap_.end(), HpaeSoftLinkState::INVALID,
376 "invalid param");
377 return streamStateMap_[sessionId];
378 }
379 } // namespace HPAE
380 } // namespace AudioStandard
381 } // namespace OHOS
382