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 "NapiCapturerReadDataCallback"
17 #endif
18 
19 #include "napi_audio_capturer_read_data_callback.h"
20 #include "audio_capturer_log.h"
21 
22 namespace OHOS {
23 namespace AudioStandard {
24 static const int32_t READ_CALLBACK_TIMEOUT_IN_MS = 1000; // 1s
25 
NapiCapturerReadDataCallback(napi_env env,NapiAudioCapturer * napiCapturer)26 NapiCapturerReadDataCallback::NapiCapturerReadDataCallback(napi_env env, NapiAudioCapturer *napiCapturer)
27     : env_(env), napiCapturer_(napiCapturer)
28 {
29     AUDIO_DEBUG_LOG("instance create");
30 }
31 
~NapiCapturerReadDataCallback()32 NapiCapturerReadDataCallback::~NapiCapturerReadDataCallback()
33 {
34     AUDIO_DEBUG_LOG("instance destroy");
35     if (napiCapturer_ != nullptr) {
36         napiCapturer_->readCallbackCv_.notify_all();
37     }
38 }
39 
AddCallbackReference(const std::string & callbackName,napi_value args)40 void NapiCapturerReadDataCallback::AddCallbackReference(const std::string &callbackName, napi_value args)
41 {
42     std::lock_guard<std::mutex> lock(mutex_);
43     napi_ref callback = nullptr;
44     const int32_t refCount = 1;
45     napi_status status = napi_create_reference(env_, args, refCount, &callback);
46     CHECK_AND_RETURN_LOG(status == napi_ok && callback != nullptr, "creating reference for callback failed");
47 
48     std::shared_ptr<AutoRef> cb = std::make_shared<AutoRef>(env_, callback);
49     if (callbackName == READ_DATA_CALLBACK_NAME) {
50         capturerReadDataCallback_ = cb;
51         callback_ = callback;
52         isCallbackInited_ = true;
53     } else {
54         AUDIO_ERR_LOG("Unknown callback type: %{public}s", callbackName.c_str());
55     }
56 }
57 
RemoveCallbackReference(napi_env env,napi_value callback)58 void NapiCapturerReadDataCallback::RemoveCallbackReference(napi_env env, napi_value callback)
59 {
60     std::lock_guard<std::mutex> lock(mutex_);
61     isCallbackInited_ = false;
62     bool isEquals = false;
63     napi_value copyValue = nullptr;
64 
65     if (callback == nullptr) {
66         napi_status ret = napi_delete_reference(env, callback_);
67         CHECK_AND_RETURN_LOG(napi_ok == ret, "delete callback reference failed");
68         AUDIO_INFO_LOG("Remove Js Callback");
69         capturerReadDataCallback_->cb_ = nullptr;
70         return;
71     }
72 
73     napi_get_reference_value(env, callback_, &copyValue);
74     CHECK_AND_RETURN_LOG(copyValue != nullptr, "copyValue is nullptr");
75     CHECK_AND_RETURN_LOG(napi_strict_equals(env, callback, copyValue, &isEquals) == napi_ok,
76         "get napi_strict_equals failed");
77     if (isEquals) {
78         AUDIO_INFO_LOG("found JS Callback, delete it!");
79         napi_status status = napi_delete_reference(env, capturerReadDataCallback_->cb_);
80         CHECK_AND_RETURN_LOG(status == napi_ok, "deleting reference for callback failed");
81         capturerReadDataCallback_->cb_ = nullptr;
82     }
83 }
84 
OnReadData(size_t length)85 void NapiCapturerReadDataCallback::OnReadData(size_t length)
86 {
87     std::lock_guard<std::mutex> lock(mutex_);
88     CHECK_AND_RETURN_LOG(capturerReadDataCallback_ != nullptr, "Cannot find the reference of readData callback");
89 
90     std::unique_ptr<CapturerReadDataJsCallback> cb = std::make_unique<CapturerReadDataJsCallback>();
91     cb->callback = capturerReadDataCallback_;
92     cb->callbackName = READ_DATA_CALLBACK_NAME;
93     cb->bufDesc.buffer = nullptr;
94     cb->capturerNapiObj = napiCapturer_;
95     cb->readDataCallbackPtr = this;
96 
97     CHECK_AND_RETURN_LOG(napiCapturer_ != nullptr, "Cannot find the reference to audio capturer napi");
98     napiCapturer_->audioCapturer_->GetBufferDesc(cb->bufDesc);
99     if (cb->bufDesc.buffer == nullptr) {
100         return;
101     }
102     if (length > cb->bufDesc.bufLength) {
103         cb->bufDesc.dataLength = cb->bufDesc.bufLength;
104     } else {
105         cb->bufDesc.dataLength = length;
106     }
107 
108     return OnJsCapturerReadDataCallback(cb);
109 }
110 
OnJsCapturerReadDataCallback(std::unique_ptr<CapturerReadDataJsCallback> & jsCb)111 void NapiCapturerReadDataCallback::OnJsCapturerReadDataCallback(std::unique_ptr<CapturerReadDataJsCallback> &jsCb)
112 {
113     uv_loop_s *loop = nullptr;
114     napi_get_uv_event_loop(env_, &loop);
115     CHECK_AND_RETURN_LOG(loop != nullptr, "loop is nullptr");
116 
117     uv_work_t *work = new(std::nothrow) uv_work_t;
118     CHECK_AND_RETURN_LOG(work != nullptr, "readData Js Callback: No memory");
119 
120     if (jsCb.get() == nullptr) {
121         AUDIO_ERR_LOG("readData Js Callback is null");
122         delete work;
123         return;
124     }
125 
126     auto obj = static_cast<NapiAudioCapturer *>(napiCapturer_);
127     ObjectRefMap<NapiAudioCapturer>::IncreaseRef(obj);
128     work->data = reinterpret_cast<void *>(jsCb.get());
129 
130     int ret = uv_queue_work_with_qos(loop, work, [] (uv_work_t *work) {},
131         WorkCallbackCapturerReadData, uv_qos_default);
132     if (ret != 0) {
133         AUDIO_ERR_LOG("Failed to execute uv work queue");
134         delete work;
135     } else {
136         jsCb.release();
137     }
138 
139     if (napiCapturer_ == nullptr) {
140         return;
141     }
142     std::unique_lock<std::mutex> readCallbackLock(napiCapturer_->readCallbackMutex_);
143     std::cv_status cvStatus = napiCapturer_->readCallbackCv_.wait_for(readCallbackLock,
144         std::chrono::milliseconds(READ_CALLBACK_TIMEOUT_IN_MS));
145     if (cvStatus == std::cv_status::timeout) {
146         AUDIO_ERR_LOG("Client OnReadData operation timed out");
147     }
148     readCallbackLock.unlock();
149 }
150 
WorkCallbackCapturerReadData(uv_work_t * work,int status)151 void NapiCapturerReadDataCallback::WorkCallbackCapturerReadData(uv_work_t *work, int status)
152 {
153     // Js Thread
154     std::shared_ptr<CapturerReadDataJsCallback> context(
155         static_cast<CapturerReadDataJsCallback*>(work->data),
156         [work](CapturerReadDataJsCallback* ptr) {
157             delete ptr;
158             delete work;
159     });
160     WorkCallbackCapturerReadDataInner(work, status);
161 
162     CHECK_AND_RETURN_LOG(work != nullptr, "capturer read data work is nullptr");
163     CapturerReadDataJsCallback *event = reinterpret_cast<CapturerReadDataJsCallback *>(work->data);
164     CHECK_AND_RETURN_LOG(event != nullptr, "capturer read data event is nullptr");
165     CHECK_AND_RETURN_LOG(event->capturerNapiObj != nullptr, "NapiAudioCapturer object is nullptr");
166     event->capturerNapiObj->readCallbackCv_.notify_all();
167     auto napiObj = static_cast<NapiAudioCapturer *>(event->capturerNapiObj);
168     ObjectRefMap<NapiAudioCapturer>::DecreaseRef(napiObj);
169 }
170 
WorkCallbackCapturerReadDataInner(uv_work_t * work,int status)171 void NapiCapturerReadDataCallback::WorkCallbackCapturerReadDataInner(uv_work_t *work, int status)
172 {
173     CHECK_AND_RETURN_LOG(work != nullptr, "capture read data work is nullptr");
174     CapturerReadDataJsCallback *event = reinterpret_cast<CapturerReadDataJsCallback *>(work->data);
175     CHECK_AND_RETURN_LOG(event != nullptr, "capture read data event is nullptr");
176     CHECK_AND_RETURN_LOG(event->readDataCallbackPtr != nullptr, "CapturerReadDataCallback is already released");
177     CHECK_AND_RETURN_LOG(event->readDataCallbackPtr->isCallbackInited_, "the callback has been dereferenced");
178     std::string request = event->callbackName;
179     CHECK_AND_RETURN_LOG(event->callback != nullptr, "event is nullptr");
180     napi_env env = event->callback->env_;
181     napi_ref callback = event->callback->cb_;
182 
183     napi_handle_scope scope = nullptr;
184     napi_open_handle_scope(env, &scope);
185     CHECK_AND_RETURN_LOG(scope != nullptr, "%{public}s scope is nullptr", request.c_str());
186     do {
187         CHECK_AND_BREAK_LOG(status != UV_ECANCELED, "%{public}s canceled", request.c_str());
188 
189         napi_value jsCallback = nullptr;
190         napi_status nstatus = napi_get_reference_value(env, callback, &jsCallback);
191         CHECK_AND_BREAK_LOG(nstatus == napi_ok && jsCallback != nullptr, "%{public}s get reference value failed",
192             request.c_str());
193         napi_value args[ARGS_ONE] = { nullptr };
194         nstatus = napi_create_external_arraybuffer(env, event->bufDesc.buffer, event->bufDesc.dataLength,
195             [](napi_env env, void *data, void *hint) {}, nullptr, &args[PARAM0]);
196         CHECK_AND_BREAK_LOG(nstatus == napi_ok && args[PARAM0] != nullptr,
197             "%{public}s callback fail to create buffer", request.c_str());
198         const size_t argCount = 1;
199         napi_value result = nullptr;
200         nstatus = napi_call_function(env, nullptr, jsCallback, argCount, args, &result);
201         CHECK_AND_BREAK_LOG(nstatus == napi_ok, "fail to call %{public}s callback", request.c_str());
202 
203         CHECK_AND_BREAK_LOG(event->capturerNapiObj != nullptr && event->capturerNapiObj->audioCapturer_ != nullptr,
204             "audioCapturer_ is null");
205         event->capturerNapiObj->audioCapturer_->Enqueue(event->bufDesc);
206     } while (0);
207     napi_close_handle_scope(env, scope);
208 }
209 } // namespace AudioStandard
210 } // namespace OHOS
211