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