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
16 #include <condition_variable>
17 #include <display_manager.h>
18 #include <jpeglib.h>
19 #include <memory>
20 #include <mutex>
21 #include <pixel_map.h>
22 #include <refbase.h>
23 #include <screen_manager.h>
24 #include <securec.h>
25 #include <csetjmp>
26 #include "common_utilities_hpp.h"
27 #include "screen_copy.h"
28
29 namespace OHOS::uitest {
30 using namespace std;
31 using namespace OHOS;
32 using namespace OHOS::Media;
33 using namespace OHOS::Rosen;
34
35 using OnScreenChangeHandler = function<void()>;
36 class ScreenChangeListener : public ScreenManager::IScreenListener {
37 public:
ScreenChangeListener(OnScreenChangeHandler hdl,ScreenId id)38 explicit ScreenChangeListener(OnScreenChangeHandler hdl, ScreenId id): handler_(hdl), targetId_(id) {}
OnConnect(ScreenId id)39 void OnConnect(ScreenId id) override {};
OnDisconnect(ScreenId id)40 void OnDisconnect(ScreenId id) override {};
OnChange(ScreenId id)41 void OnChange(ScreenId id) override
42 {
43 if (handler_ != nullptr && id == targetId_) {
44 handler_();
45 }
46 };
Destroy()47 void Destroy() { handler_ = nullptr; }
48 private:
49 OnScreenChangeHandler handler_ = nullptr;
50 ScreenId targetId_ = SCREEN_ID_INVALID;
51 };
52
53 class ScreenCopy {
54 public:
ScreenCopy(float scale)55 explicit ScreenCopy(float scale): scale_(scale) {};
56 virtual ~ScreenCopy();
57 bool Run(int32_t displayId);
58 void Destroy();
59 const char* pendingError_ = nullptr;
60 const float scale_ = 0.5f;
61 private:
62 void PollAndNotifyFrames(int32_t displayId);
63 void WaitAndConsumeFrames();
64 void UpdateFrameLocked(shared_ptr<PixelMap> frame, bool &changed, bool &muted);
65 shared_ptr<PixelMap> ScaleNewsetFrameLocked();
66 sptr<Screen> sourceScreen_;
67 shared_ptr<PixelMap> lastFrame_ = nullptr;
68 shared_ptr<PixelMap> newestFrame_ = nullptr;
69 mutex frameLock_;
70 condition_variable frameCond_;
71 unique_ptr<thread> snapshotThread = nullptr;
72 unique_ptr<thread> encodeThread = nullptr;
73 atomic_bool stopped_ = false;
74 static sptr<ScreenChangeListener> screenChangeListener_;
75 };
76 sptr<ScreenChangeListener> ScreenCopy::screenChangeListener_ = nullptr;
77 static unique_ptr<ScreenCopy> g_screenCopy = nullptr;
78 static ScreenCopyHandler g_screenCopyHandler = nullptr;
79
AdapteScreenChange(int32_t displayId)80 static void AdapteScreenChange(int32_t displayId)
81 {
82 if (g_screenCopy == nullptr) {
83 return;
84 }
85 // destrory current one and create a new one
86 LOG_D("Screen changed, auto restart ScreenCopy");
87 const auto scale = g_screenCopy->scale_;
88 g_screenCopy->Destroy();
89 g_screenCopy = make_unique<ScreenCopy>(scale);
90 g_screenCopy->Run(displayId);
91 }
92
~ScreenCopy()93 ScreenCopy::~ScreenCopy()
94 {
95 if (!stopped_.load()) {
96 Destroy();
97 }
98 }
99
Run(int32_t displayId)100 bool ScreenCopy::Run(int32_t displayId)
101 {
102 if (scale_ <= 0 || scale_ > 1.0) {
103 pendingError_ = "Error: Illegal scale value!";
104 return false;
105 }
106 // get source screen
107 if (displayId == UNASSIGNED) {
108 displayId = DisplayManager::GetInstance().GetDefaultDisplayId();
109 }
110 sourceScreen_ = ScreenManager::GetInstance().GetScreenById(displayId);
111 if (displayId == SCREEN_ID_INVALID || sourceScreen_ == nullptr) {
112 pendingError_ = "Error: Get main screen failed!";
113 return false;
114 }
115 // listen screen changes for auto-adapting
116 if (screenChangeListener_ == nullptr) {
117 screenChangeListener_ = new ScreenChangeListener([displayId]() { AdapteScreenChange(displayId); }, displayId);
118 auto ret = ScreenManager::GetInstance().RegisterScreenListener(screenChangeListener_);
119 LOG_D("Register ScreenListener, ret=%{public}d", ret);
120 }
121 // run snapshot thread and encode thread
122 snapshotThread = make_unique<thread>([this, displayId]() { this->PollAndNotifyFrames(displayId); });
123 encodeThread = make_unique<thread>([this]() { this->WaitAndConsumeFrames(); });
124 return true;
125 }
126
Destroy()127 void ScreenCopy::Destroy()
128 {
129 if (stopped_.load()) {
130 return;
131 }
132 unique_lock<mutex> lock(frameLock_);
133 stopped_.store(true);
134 frameCond_.notify_all(); // mark stopped and wakeup the waiting thread
135 lock.unlock();
136 LOG_D("Begin to wait for threads exit");
137 if (snapshotThread != nullptr && snapshotThread->joinable()) {
138 snapshotThread->join();
139 snapshotThread = nullptr;
140 }
141 sourceScreen_ = nullptr;
142 lastFrame_ = nullptr;
143 newestFrame_ = nullptr;
144 if (encodeThread != nullptr && encodeThread->joinable()) {
145 encodeThread->join();
146 encodeThread = nullptr;
147 }
148 LOG_D("All threads exited");
149 }
150
PollAndNotifyFrames(int32_t displayId)151 void ScreenCopy::PollAndNotifyFrames(int32_t displayId)
152 {
153 constexpr int32_t screenCheckIntervalUs = 50 * 1000;
154 auto &dm = DisplayManager::GetInstance();
155 LOG_I("Start PollAndNotifyFrames");
156 bool changed = false;
157 bool screenOff = false;
158 while (!stopped_.load()) {
159 if (screenOff) {
160 usleep(screenCheckIntervalUs);
161 if (dm.GetDisplayState(displayId) != DisplayState::OFF) {
162 screenOff = false;
163 LOG_I("Screen turned on! resume screenCopy");
164 }
165 continue;
166 }
167 shared_ptr<PixelMap> frame = dm.GetScreenshot(displayId);
168 if (frame == nullptr) {
169 continue;
170 }
171 frameLock_.lock();
172 UpdateFrameLocked(frame, changed, screenOff);
173 frameLock_.unlock();
174 LOG_D("GetOneFrameDone, Changed=%{public}d", changed);
175 if (changed) {
176 frameCond_.notify_all();
177 }
178 if (screenOff) {
179 LOG_I("Screen turned off! mute screenCopy");
180 }
181 }
182 }
183
UpdateFrameLocked(shared_ptr<PixelMap> frame,bool & changed,bool & screenOff)184 void ScreenCopy::UpdateFrameLocked(shared_ptr<PixelMap> frame, bool &changed, bool &screenOff)
185 {
186 DCHECK(sourceScreen_);
187 lastFrame_ = newestFrame_;
188 newestFrame_ = frame;
189 changed = true;
190 const size_t newestFrameSize = newestFrame_->GetHeight() * newestFrame_->GetRowStride();
191 // if this is the first frame
192 if (lastFrame_ == nullptr) {
193 // if screen copy starts with screen-off, given a black image
194 if (DisplayManager::GetInstance().GetDisplayState(sourceScreen_->GetId()) == DisplayState::OFF) {
195 memset_s(frame->GetWritablePixels(), newestFrameSize, 0, newestFrameSize);
196 }
197 return;
198 }
199 // compare this frame and last frame
200 const size_t lastFrameSize = lastFrame_->GetHeight() * lastFrame_->GetRowStride();
201 if (lastFrameSize == newestFrameSize) {
202 changed = memcmp(lastFrame_->GetPixels(), newestFrame_->GetPixels(), newestFrameSize) != 0;
203 }
204 // detect screen of only when not changed
205 if (!changed && !screenOff) {
206 screenOff = DisplayManager::GetInstance().GetDisplayState(sourceScreen_->GetId()) == DisplayState::OFF;
207 if (screenOff) {
208 // mark changed and reset pixels to black so we provide a black image
209 changed = true;
210 memset_s(frame->GetWritablePixels(), newestFrameSize, 0, newestFrameSize);
211 }
212 }
213 }
214
215 struct MissionErrorMgr : public jpeg_error_mgr {
216 jmp_buf setjmp_buffer;
217 };
218
AdaptJpegSize(jpeg_compress_struct & jpeg,uint32_t width,uint32_t height)219 static void AdaptJpegSize(jpeg_compress_struct &jpeg, uint32_t width, uint32_t height)
220 {
221 constexpr int32_t alignment = 32;
222 if (width % alignment == 0) {
223 jpeg.image_width = width;
224 } else {
225 LOG_D("The width need to be adapted!");
226 jpeg.image_width = ceil((double)width / (double)alignment) * alignment;
227 }
228 jpeg.image_height = height;
229 }
230
ScaleNewsetFrameLocked()231 shared_ptr<PixelMap> ScreenCopy::ScaleNewsetFrameLocked()
232 {
233 if (newestFrame_ == nullptr) {
234 return newestFrame_;
235 }
236 // resize the pixelmap to fit scale
237 auto origWidth = newestFrame_->GetWidth();
238 auto origHeight = newestFrame_->GetHeight();
239 Media::Rect rect = {.left = 0, .top = 0, .width = origWidth, .height = origHeight};
240 Media::InitializationOptions opt;
241 opt.size.width = ceil(origWidth * scale_);
242 opt.size.height = ceil(origHeight * scale_);
243 opt.scaleMode = Media::ScaleMode::FIT_TARGET_SIZE;
244 opt.editable = false;
245 return Media::PixelMap::Create(*newestFrame_, rect, opt);
246 }
247
WaitAndConsumeFrames()248 void ScreenCopy::WaitAndConsumeFrames()
249 {
250 LOG_I("Start WaitAndConsumeFrames");
251 while (!stopped_.load()) {
252 unique_lock<mutex> lock(frameLock_);
253 frameCond_.wait(lock);
254 if (stopped_.load()) {
255 break;
256 }
257 LOG_D("ConsumeFrame_Begin");
258 // resize the pixelmap to fit scale
259 auto scaledPixels = ScaleNewsetFrameLocked();
260 lock.unlock();
261 if (scaledPixels == nullptr) {
262 continue;
263 }
264 constexpr int32_t rgbaPixelBytes = 4;
265 jpeg_compress_struct jpeg;
266 MissionErrorMgr jerr;
267 jpeg.err = jpeg_std_error(&jerr);
268 jpeg_create_compress(&jpeg);
269 AdaptJpegSize(jpeg, scaledPixels->GetWidth(), scaledPixels->GetHeight());
270 jpeg.input_components = rgbaPixelBytes;
271 jpeg.in_color_space = JCS_EXT_RGBX;
272 jpeg_set_defaults(&jpeg);
273 constexpr int32_t compressQuality = 75;
274 jpeg_set_quality(&jpeg, compressQuality, 1);
275 uint8_t *imgBuf = nullptr;
276 unsigned long imgSize = 0;
277 jpeg_mem_dest(&jpeg, &imgBuf, &imgSize);
278 jpeg_start_compress(&jpeg, 1);
279 JSAMPROW rowPointer[1024 * 4];
280 const auto stride = scaledPixels->GetRowStride();
281 auto memAddr = (uint8_t *)(scaledPixels->GetPixels());
282 for (int32_t rowIndex = 0; rowIndex < scaledPixels->GetHeight(); rowIndex++) {
283 rowPointer[rowIndex] = memAddr;
284 memAddr += stride;
285 }
286 (void)jpeg_write_scanlines(&jpeg, rowPointer, jpeg.image_height);
287 jpeg_finish_compress(&jpeg);
288 jpeg_destroy_compress(&jpeg);
289 LOG_D("ConsumeFrame_End, size=%{public}lu", imgSize);
290 if (g_screenCopyHandler != nullptr) {
291 g_screenCopyHandler(imgBuf, imgSize);
292 } else {
293 free(imgBuf);
294 }
295 }
296 LOG_I("Stop WaitAndConsumeFrames");
297 }
298
StartScreenCopy(float scale,int32_t displayId,ScreenCopyHandler handler)299 bool StartScreenCopy(float scale, int32_t displayId, ScreenCopyHandler handler)
300 {
301 if (scale <= 0 || scale > 1 || handler == nullptr) {
302 LOG_E("Illegal arguments");
303 return false;
304 }
305 StopScreenCopy();
306 g_screenCopyHandler = handler;
307 g_screenCopy = make_unique<ScreenCopy>(scale);
308 bool success = g_screenCopy != nullptr && g_screenCopy->Run(displayId);
309 if (!success) {
310 constexpr size_t BUF_SIZE = 128;
311 auto buf = (uint8_t *)malloc(BUF_SIZE);
312 memset_s(buf, BUF_SIZE, 0, BUF_SIZE);
313 if (g_screenCopy->pendingError_ != nullptr) {
314 g_screenCopy->pendingError_ = "Failed to run ScreenCopy, unknown error";
315 }
316 memcpy_s(buf, BUF_SIZE, g_screenCopy->pendingError_, strlen(g_screenCopy->pendingError_));
317 LOG_E("The error message is %{public}s", buf);
318 handler(buf, strlen(g_screenCopy->pendingError_));
319 g_screenCopy->pendingError_ = nullptr;
320 StopScreenCopy();
321 }
322 return success;
323 }
324
StopScreenCopy()325 void StopScreenCopy()
326 {
327 if (g_screenCopy != nullptr) {
328 g_screenCopy->Destroy();
329 g_screenCopy = nullptr;
330 }
331 }
332 }
333