1 /*
2 * Copyright (c) 2023 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 #ifndef LOG_TAG
16 #define LOG_TAG "NapiRendererWriteDataCallback"
17 #endif
18
19 #include "js_native_api.h"
20 #include "napi_audio_renderer_write_data_callback.h"
21 #include "audio_renderer_log.h"
22 #include "napi_audio_enum.h"
23
24 namespace OHOS {
25 namespace AudioStandard {
26 static const int32_t WRITE_CALLBACK_TIMEOUT_IN_MS = 1000; // 1s
27
28 #if defined(ANDROID_PLATFORM) || defined(IOS_PLATFORM)
29 vector<NapiAudioRenderer*> NapiRendererWriteDataCallback::activeRenderers_;
30 #endif
NapiRendererWriteDataCallback(napi_env env,NapiAudioRenderer * napiRenderer)31 NapiRendererWriteDataCallback::NapiRendererWriteDataCallback(napi_env env, NapiAudioRenderer *napiRenderer)
32 : env_(env), napiRenderer_(napiRenderer)
33 {
34 AUDIO_DEBUG_LOG("instance create");
35 #if defined(ANDROID_PLATFORM) || defined(IOS_PLATFORM)
36 activeRenderers_.emplace_back(napiRenderer_);
37 #endif
38 }
39
~NapiRendererWriteDataCallback()40 NapiRendererWriteDataCallback::~NapiRendererWriteDataCallback()
41 {
42 AUDIO_DEBUG_LOG("instance destroy");
43 #if defined(ANDROID_PLATFORM) || defined(IOS_PLATFORM)
44 auto iter = std::find(activeRenderers_.begin(), activeRenderers_.end(), napiRenderer_);
45 if (iter != activeRenderers_.end()) {
46 activeRenderers_.erase(iter);
47 }
48 #endif
49 if (napiRenderer_ != nullptr) {
50 napiRenderer_->writeCallbackCv_.notify_all();
51 }
52 if (regArWriteDataTsfn_) {
53 napi_release_threadsafe_function(arWriteDataTsfn_, napi_tsfn_abort);
54 }
55 }
56
AddCallbackReference(const std::string & callbackName,napi_value args)57 void NapiRendererWriteDataCallback::AddCallbackReference(const std::string &callbackName, napi_value args)
58 {
59 std::lock_guard<std::mutex> lock(mutex_);
60 napi_ref callback = nullptr;
61 const int32_t refCount = 1;
62 napi_status status = napi_create_reference(env_, args, refCount, &callback);
63 CHECK_AND_RETURN_LOG(status == napi_ok && callback != nullptr, "creating reference for callback failed");
64
65 std::shared_ptr<AutoRef> cb = std::make_shared<AutoRef>(env_, callback);
66 if (callbackName == WRITE_DATA_CALLBACK_NAME) {
67 rendererWriteDataCallback_ = cb;
68 } else {
69 AUDIO_ERR_LOG("Unknown callback type: %{public}s", callbackName.c_str());
70 }
71 }
72
CreateWriteDTsfn(napi_env env)73 void NapiRendererWriteDataCallback::CreateWriteDTsfn(napi_env env)
74 {
75 regArWriteDataTsfn_ = true;
76 std::string callbackName = "writeData";
77 napi_value cbName;
78 napi_create_string_utf8(env, callbackName.c_str(), callbackName.length(), &cbName);
79 napi_create_threadsafe_function(env, nullptr, nullptr, cbName, 0, 1, nullptr,
80 WriteDataTsfnFinalize, nullptr, SafeJsCallbackWriteDataWork, &arWriteDataTsfn_);
81 }
82
GetWriteDTsfnFlag()83 bool NapiRendererWriteDataCallback::GetWriteDTsfnFlag()
84 {
85 return regArWriteDataTsfn_;
86 }
87
RemoveCallbackReference(napi_env env,napi_value callback)88 void NapiRendererWriteDataCallback::RemoveCallbackReference(napi_env env, napi_value callback)
89 {
90 std::lock_guard<std::mutex> lock(mutex_);
91 bool isEquals = false;
92 napi_value copyValue = nullptr;
93
94 if (callback == nullptr) {
95 napi_status ret = napi_delete_reference(env, rendererWriteDataCallback_->cb_);
96 CHECK_AND_RETURN_LOG(napi_ok == ret, "delete callback reference failed");
97 rendererWriteDataCallback_->cb_ = nullptr;
98 AUDIO_INFO_LOG("Remove Js Callback");
99 return;
100 }
101
102 napi_get_reference_value(env, rendererWriteDataCallback_->cb_, ©Value);
103 CHECK_AND_RETURN_LOG(copyValue != nullptr, "copyValue is nullptr");
104 CHECK_AND_RETURN_LOG(napi_strict_equals(env, callback, copyValue, &isEquals) == napi_ok,
105 "get napi_strict_equals failed");
106 if (isEquals) {
107 AUDIO_INFO_LOG("found Js Callback, delete it!");
108 napi_status status = napi_delete_reference(env, rendererWriteDataCallback_->cb_);
109 CHECK_AND_RETURN_LOG(status == napi_ok, "deleting reference for callback failed");
110 rendererWriteDataCallback_->cb_ = nullptr;
111 }
112 }
113
OnWriteData(size_t length)114 void NapiRendererWriteDataCallback::OnWriteData(size_t length)
115 {
116 std::lock_guard<std::mutex> lock(mutex_);
117 CHECK_AND_RETURN_LOG(rendererWriteDataCallback_ != nullptr, "Cannot find the reference of writeData callback");
118
119 std::unique_ptr<RendererWriteDataJsCallback> cb = std::make_unique<RendererWriteDataJsCallback>();
120 cb->callback = rendererWriteDataCallback_;
121 cb->callbackName = WRITE_DATA_CALLBACK_NAME;
122 cb->bufDesc.buffer = nullptr;
123 cb->rendererNapiObj = napiRenderer_;
124
125 CHECK_AND_RETURN_LOG(napiRenderer_ != nullptr, "Cannot find the reference to audio renderer napi");
126 if (!napiRenderer_->audioRenderer_) {
127 AUDIO_INFO_LOG("OnWriteData audioRenderer_ is null.");
128 return;
129 }
130 napiRenderer_->audioRenderer_->GetBufferDesc(cb->bufDesc);
131 if (cb->bufDesc.buffer == nullptr) {
132 return;
133 }
134 if (length > cb->bufDesc.bufLength) {
135 cb->bufDesc.dataLength = cb->bufDesc.bufLength;
136 } else {
137 cb->bufDesc.dataLength = length;
138 }
139
140 return OnJsRendererWriteDataCallback(cb);
141 }
142
OnJsRendererWriteDataCallback(std::unique_ptr<RendererWriteDataJsCallback> & jsCb)143 void NapiRendererWriteDataCallback::OnJsRendererWriteDataCallback(std::unique_ptr<RendererWriteDataJsCallback> &jsCb)
144 {
145 if (jsCb.get() == nullptr) {
146 AUDIO_ERR_LOG("OnJsRendererWriteDataCallback: jsCb.get() is null");
147 return;
148 }
149
150 RendererWriteDataJsCallback *event = jsCb.release();
151 CHECK_AND_RETURN_LOG((event != nullptr) && (event->callback != nullptr), "event is nullptr.");
152
153 auto obj = static_cast<NapiAudioRenderer *>(napiRenderer_);
154 NapiAudioRenderer *napiRenderer = ObjectRefMap<NapiAudioRenderer>::IncreaseRef(obj);
155 if (napiRenderer == nullptr) {
156 AUDIO_ERR_LOG("napiRenderer is null");
157 return;
158 }
159
160 napi_acquire_threadsafe_function(arWriteDataTsfn_);
161 napi_call_threadsafe_function(arWriteDataTsfn_, event, napi_tsfn_blocking);
162
163 if (napiRenderer_ == nullptr) {
164 return;
165 }
166 std::unique_lock<std::mutex> writeCallbackLock(napiRenderer_->writeCallbackMutex_);
167 std::cv_status cvStatus = napiRenderer_->writeCallbackCv_.wait_for(writeCallbackLock,
168 std::chrono::milliseconds(WRITE_CALLBACK_TIMEOUT_IN_MS));
169 if (cvStatus == std::cv_status::timeout) {
170 AUDIO_ERR_LOG("Client OnWriteData operation timed out");
171 }
172 writeCallbackLock.unlock();
173 }
174
CheckWriteDataCallbackResult(napi_env env,BufferDesc & bufDesc,napi_value result)175 void NapiRendererWriteDataCallback::CheckWriteDataCallbackResult(napi_env env, BufferDesc &bufDesc, napi_value result)
176 {
177 napi_valuetype resultType = napi_undefined;
178 napi_typeof(env, result, &resultType);
179 if (resultType == napi_number) {
180 int32_t resultIntValue;
181 napi_get_value_int32(env, result, &resultIntValue);
182 auto resultValue = static_cast<NapiAudioEnum::AudioDataCallbackResult>(resultIntValue);
183 if (resultValue == NapiAudioEnum::CALLBACK_RESULT_INVALID) {
184 AUDIO_DEBUG_LOG("Data callback returned invalid, data will not be used.");
185 bufDesc.dataLength = 0; // Ensure that the invalid data is not used.
186 }
187 }
188 }
189
SafeJsCallbackWriteDataWork(napi_env env,napi_value js_cb,void * context,void * data)190 void NapiRendererWriteDataCallback::SafeJsCallbackWriteDataWork(
191 napi_env env, napi_value js_cb, void *context, void *data)
192 {
193 RendererWriteDataJsCallback *event = reinterpret_cast<RendererWriteDataJsCallback *>(data);
194 CHECK_AND_RETURN_LOG((event != nullptr) && (event->callback != nullptr), "event is nullptr.");
195 std::shared_ptr<RendererWriteDataJsCallback> safeContext(
196 static_cast<RendererWriteDataJsCallback*>(data),
197 [](RendererWriteDataJsCallback *ptr) {
198 delete ptr;
199 });
200 WorkCallbackRendererWriteDataInner(event);
201 CHECK_AND_RETURN_LOG(event->rendererNapiObj != nullptr, "NapiAudioRenderer object is nullptr");
202 event->rendererNapiObj->writeCallbackCv_.notify_all();
203 auto napiObj = static_cast<NapiAudioRenderer *>(event->rendererNapiObj);
204 ObjectRefMap<NapiAudioRenderer>::DecreaseRef(napiObj);
205 }
206
WriteDataTsfnFinalize(napi_env env,void * data,void * hint)207 void NapiRendererWriteDataCallback::WriteDataTsfnFinalize(napi_env env, void *data, void *hint)
208 {
209 AUDIO_DEBUG_LOG("WriteDataTsfnFinalize: safe thread resource release.");
210 }
211
WorkCallbackRendererWriteDataInner(RendererWriteDataJsCallback * event)212 void NapiRendererWriteDataCallback::WorkCallbackRendererWriteDataInner(RendererWriteDataJsCallback *event)
213 {
214 CHECK_AND_RETURN_LOG((event != nullptr) && (event->callback != nullptr),
215 "OnJsRendererWriteDataCallback: no memory");
216 std::string request = event->callbackName;
217 napi_ref callback = event->callback->cb_;
218 napi_env env = event->callback->env_;
219 napi_handle_scope scope = nullptr;
220 napi_open_handle_scope(env, &scope);
221 CHECK_AND_RETURN_LOG(scope != nullptr, "%{public}s scope is nullptr", request.c_str());
222
223 do {
224 napi_value jsCallback = nullptr;
225 napi_status nstatus = napi_get_reference_value(env, callback, &jsCallback);
226 CHECK_AND_BREAK_LOG(nstatus == napi_ok && jsCallback != nullptr, "%{public}s get reference value failed",
227 request.c_str());
228 napi_value args[ARGS_ONE] = { nullptr };
229 nstatus = napi_create_external_arraybuffer(env, event->bufDesc.buffer, event->bufDesc.dataLength,
230 [](napi_env env, void *data, void *hint) {}, nullptr, &args[PARAM0]);
231 CHECK_AND_BREAK_LOG(nstatus == napi_ok && args[PARAM0] != nullptr,
232 "%{public}s callback fail to create buffer", request.c_str());
233 const size_t argCount = 1;
234 napi_value result = nullptr;
235 nstatus = napi_call_function(env, nullptr, jsCallback, argCount, args, &result);
236 CHECK_AND_BREAK_LOG(nstatus == napi_ok, "fail to call %{public}s callback", request.c_str());
237 CheckWriteDataCallbackResult(env, event->bufDesc, result);
238 #if defined(ANDROID_PLATFORM) || defined(IOS_PLATFORM)
239 auto iter = std::find(activeRenderers_.begin(), activeRenderers_.end(), event->rendererNapiObj);
240 if (iter != activeRenderers_.end()) {
241 if (event->rendererNapiObj->audioRenderer_) {
242 event->rendererNapiObj->audioRenderer_->Enqueue(event->bufDesc);
243 } else {
244 AUDIO_INFO_LOG("WorkCallbackRendererWriteData audioRenderer_ is null");
245 }
246 } else {
247 AUDIO_INFO_LOG("NapiRendererWriteDataCallback is finalize.");
248 }
249 #else
250 event->rendererNapiObj->audioRenderer_->Enqueue(event->bufDesc);
251 #endif
252 } while (0);
253 napi_close_handle_scope(env, scope);
254 }
255 } // namespace AudioStandard
256 } // namespace OHOS
257