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