]> git.sesse.net Git - nageru/blob - nageru/cef_capture.cpp
Fix a dangling reference (found by GCC 14).
[nageru] / nageru / cef_capture.cpp
1 #include <assert.h>
2 #include <functional>
3 #include <map>
4 #include <mutex>
5 #include <stdio.h>
6 #include <stdint.h>
7 #include <string.h>
8 #include <chrono>
9 #include <memory>
10 #include <string>
11 #include <utility>
12
13 #include "cef_capture.h"
14 #include "base/cef_logging.h"
15 #include "nageru_cef_app.h"
16 #include "nageru/defs.h"
17
18 #undef CHECK
19 #include <cef_browser.h>
20 #include <cef_frame.h>
21 #include <cef_task.h>
22
23 #include "bmusb/bmusb.h"
24
25 using namespace std;
26 using namespace std::chrono;
27 using namespace bmusb;
28
29 extern CefRefPtr<NageruCefApp> cef_app;
30
31 CEFCapture::CEFCapture(const string &url, unsigned width, unsigned height)
32         : cef_client(new NageruCEFClient(this)),
33           width(width),
34           height(height),
35           start_url(url)
36 {
37         char buf[256];
38         snprintf(buf, sizeof(buf), "CEF card %d", card_index + 1);
39         description = buf;
40 }
41
42 CEFCapture::~CEFCapture()
43 {
44         if (has_dequeue_callbacks) {
45                 dequeue_cleanup_callback();
46         }
47 }
48
49 void CEFCapture::post_to_cef_ui_thread(std::function<void()> &&func, int64_t delay_ms)
50 {
51         lock_guard<recursive_mutex> lock(browser_mutex);
52         if (browser != nullptr) {
53                 if (delay_ms <= 0) {
54                         CefPostTask(TID_UI, new CEFTaskAdapter(std::move(func)));
55                 } else {
56                         CefPostDelayedTask(TID_UI, new CEFTaskAdapter(std::move(func)), delay_ms);
57                 }
58         } else {
59                 deferred_tasks.push_back(std::move(func));
60         }
61 }
62
63 void CEFCapture::set_url(const string &url)
64 {
65         post_to_cef_ui_thread([this, url] {
66                 loaded = false;
67                 browser->GetMainFrame()->LoadURL(url);
68         });
69 }
70
71 void CEFCapture::reload()
72 {
73         post_to_cef_ui_thread([this] {
74                 loaded = false;
75                 browser->Reload();
76         });
77 }
78
79 void CEFCapture::set_max_fps(int max_fps)
80 {
81         post_to_cef_ui_thread([this, max_fps] {
82                 browser->GetHost()->SetWindowlessFrameRate(max_fps);
83                 this->max_fps = max_fps;
84         });
85 }
86
87 void CEFCapture::execute_javascript_async(const string &js)
88 {
89         post_to_cef_ui_thread([this, js] {
90                 if (loaded) {
91                         CefString script_url("<theme eval>");
92                         int start_line = 1;
93                         browser->GetMainFrame()->ExecuteJavaScript(js, script_url, start_line);
94                 } else {
95                         deferred_javascript.push_back(js);
96                 }
97         });
98 }
99
100 void CEFCapture::resize(unsigned width, unsigned height)
101 {
102         lock_guard<mutex> lock(resolution_mutex);
103         this->width = width;
104         this->height = height;
105 }
106
107 void CEFCapture::request_new_frame(bool ignore_if_locked)
108 {
109         unique_lock<recursive_mutex> outer_lock(browser_mutex, defer_lock);
110         if (ignore_if_locked && !outer_lock.try_lock()) {
111                 // If the caller is holding card_mutex, we need to abort here
112                 // if we can't get browser_mutex, since otherwise, the UI thread
113                 // might hold browser_mutex (blocking post_to_cef_ui_thread())
114                 // and be waiting for card_mutex.
115                 return;
116         }
117
118         // By adding a delay, we make sure we don't get a new frame
119         // delivered immediately (we probably already are on the UI thread),
120         // where we couldn't really deal with it.
121         post_to_cef_ui_thread([this] {
122                 lock_guard<recursive_mutex> lock(browser_mutex);
123                 if (browser != nullptr) {  // Could happen if we are shutting down.
124                         browser->GetHost()->Invalidate(PET_VIEW);
125                 }
126         }, 16);
127 }
128
129 void CEFCapture::OnPaint(const void *buffer, int width, int height)
130 {
131         steady_clock::time_point timestamp = steady_clock::now();
132
133         VideoFormat video_format;
134         video_format.width = width;
135         video_format.height = height;
136         video_format.stride = width * 4;
137         video_format.frame_rate_nom = max_fps;
138         video_format.frame_rate_den = 1;
139         video_format.has_signal = true;
140         video_format.is_connected = true;
141
142         FrameAllocator::Frame video_frame = video_frame_allocator->alloc_frame();
143         if (video_frame.data == nullptr) {
144                 // We lost a frame, so we need to invalidate the entire thing.
145                 // (CEF only sends OnPaint when there are actual changes,
146                 // so we need to do this explicitly, or we could be stuck on an
147                 // old frame forever if the image doesn't change.)
148                 request_new_frame(/*ignore_if_locked=*/false);
149                 ++timecode;
150         } else {
151                 assert(video_frame.size >= unsigned(width * height * 4));
152                 assert(!video_frame.interleaved);
153                 memcpy(video_frame.data, buffer, width * height * 4);
154                 video_frame.len = video_format.stride * height;
155                 video_frame.received_timestamp = timestamp;
156                 frame_callback(timecode++,
157                         video_frame, 0, video_format,
158                         FrameAllocator::Frame(), 0, AudioFormat());
159         }
160 }
161
162 void CEFCapture::OnLoadEnd()
163 {
164         post_to_cef_ui_thread([this] {
165                 loaded = true;
166                 for (const string &js : deferred_javascript) {
167                         CefString script_url("<theme eval>");
168                         int start_line = 1;
169                         browser->GetMainFrame()->ExecuteJavaScript(js, script_url, start_line);
170                 }
171                 deferred_javascript.clear();
172         });
173 }
174
175 void CEFCapture::configure_card()
176 {
177         if (video_frame_allocator == nullptr) {
178                 owned_video_frame_allocator.reset(new MallocFrameAllocator(FRAME_SIZE, NUM_QUEUED_VIDEO_FRAMES));
179                 set_video_frame_allocator(owned_video_frame_allocator.get());
180         }
181 }
182
183 void CEFCapture::start_bm_capture()
184 {
185         cef_app->initialize_cef();
186
187         CefPostTask(TID_UI, new CEFTaskAdapter([this]{
188                 lock_guard<recursive_mutex> lock(browser_mutex);
189
190                 CefBrowserSettings browser_settings;
191                 browser_settings.webgl = cef_state_t::STATE_ENABLED;
192                 browser_settings.windowless_frame_rate = max_fps;
193
194                 CefWindowInfo window_info;
195                 window_info.SetAsWindowless(0);
196                 browser = CefBrowserHost::CreateBrowserSync(window_info, cef_client, start_url, browser_settings, nullptr, nullptr);
197                 for (function<void()> &task : deferred_tasks) {
198                         task();
199                 }
200                 deferred_tasks.clear();
201         }));
202 }
203
204 void CEFCapture::stop_dequeue_thread()
205 {
206         {
207                 lock_guard<recursive_mutex> lock(browser_mutex);
208                 cef_app->close_browser(browser);
209                 browser = nullptr;  // Or unref_cef() will be sad.
210         }
211         cef_app->unref_cef();
212 }
213
214 std::map<uint32_t, VideoMode> CEFCapture::get_available_video_modes() const
215 {
216         VideoMode mode;
217
218         char buf[256];
219         snprintf(buf, sizeof(buf), "%ux%u", width, height);
220         mode.name = buf;
221
222         mode.autodetect = false;
223         mode.width = width;
224         mode.height = height;
225         mode.frame_rate_num = max_fps;
226         mode.frame_rate_den = 1;
227         mode.interlaced = false;
228
229         return {{ 0, mode }};
230 }
231
232 std::map<uint32_t, std::string> CEFCapture::get_available_video_inputs() const
233 {
234         return {{ 0, "HTML video input" }};
235 }
236
237 std::map<uint32_t, std::string> CEFCapture::get_available_audio_inputs() const
238 {
239         return {{ 0, "Fake HTML audio input (silence)" }};
240 }
241
242 void CEFCapture::set_video_mode(uint32_t video_mode_id)
243 {
244         assert(video_mode_id == 0);
245 }
246
247 void CEFCapture::set_video_input(uint32_t video_input_id)
248 {
249         assert(video_input_id == 0);
250 }
251
252 void CEFCapture::set_audio_input(uint32_t audio_input_id)
253 {
254         assert(audio_input_id == 0);
255 }
256
257 void NageruCEFClient::OnPaint(CefRefPtr<CefBrowser> browser, PaintElementType type, const RectList &dirtyRects, const void *buffer, int width, int height)
258 {
259         parent->OnPaint(buffer, width, height);
260 }
261
262 void NageruCEFClient::GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect)
263 {
264         parent->GetViewRect(rect);
265 }
266
267 void CEFCapture::GetViewRect(CefRect &rect)
268 {
269         lock_guard<mutex> lock(resolution_mutex);
270         rect = CefRect(0, 0, width, height);
271 }
272
273 void NageruCEFClient::OnLoadEnd(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, int httpStatusCode)
274 {
275         parent->OnLoadEnd();
276 }