8 #include "cef_capture.h"
9 #include "nageru_cef_app.h"
13 #include <cef_browser.h>
14 #include <cef_client.h>
16 #include "bmusb/bmusb.h"
19 using namespace std::chrono;
20 using namespace bmusb;
22 extern CefRefPtr<NageruCefApp> cef_app;
24 CEFCapture::CEFCapture(const string &url, unsigned width, unsigned height)
25 : cef_client(new NageruCEFClient(width, height, this)),
31 snprintf(buf, sizeof(buf), "CEF card %d", card_index + 1);
35 CEFCapture::~CEFCapture()
37 if (has_dequeue_callbacks) {
38 dequeue_cleanup_callback();
42 void CEFCapture::post_to_cef_ui_thread(std::function<void()> &&func)
44 lock_guard<recursive_mutex> lock(browser_mutex);
45 if (browser != nullptr) {
46 CefPostTask(TID_UI, new CEFTaskAdapter(std::move(func)));
48 deferred_tasks.push_back(std::move(func));
52 void CEFCapture::set_url(const string &url)
54 post_to_cef_ui_thread([this, url] {
56 browser->GetMainFrame()->LoadURL(url);
60 void CEFCapture::reload()
62 post_to_cef_ui_thread([this] {
68 void CEFCapture::set_max_fps(int max_fps)
70 post_to_cef_ui_thread([this, max_fps] {
71 browser->GetHost()->SetWindowlessFrameRate(max_fps);
72 this->max_fps = max_fps;
76 void CEFCapture::execute_javascript_async(const string &js)
78 post_to_cef_ui_thread([this, js] {
80 CefString script_url("<theme eval>");
82 browser->GetMainFrame()->ExecuteJavaScript(js, script_url, start_line);
84 deferred_javascript.push_back(js);
89 void CEFCapture::resize(unsigned width, unsigned height)
91 lock_guard<mutex> lock(resolution_mutex);
93 this->height = height;
96 void CEFCapture::OnPaint(const void *buffer, int width, int height)
98 steady_clock::time_point timestamp = steady_clock::now();
100 VideoFormat video_format;
101 video_format.width = width;
102 video_format.height = height;
103 video_format.stride = width * 4;
104 video_format.frame_rate_nom = max_fps;
105 video_format.frame_rate_den = 1;
106 video_format.has_signal = true;
107 video_format.is_connected = true;
109 FrameAllocator::Frame video_frame = video_frame_allocator->alloc_frame();
110 if (video_frame.data == nullptr) {
111 // We lost a frame, so we need to invalidate the entire thing.
112 // (CEF only sends OnPaint when there are actual changes,
113 // so we need to do this explicitly, or we could be stuck on an
114 // old frame forever if the image doesn't change.)
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);
123 assert(video_frame.size >= unsigned(width * height * 4));
124 assert(!video_frame.interleaved);
125 memcpy(video_frame.data, buffer, width * height * 4);
126 video_frame.len = video_format.stride * height;
127 video_frame.received_timestamp = timestamp;
128 frame_callback(timecode++,
129 video_frame, 0, video_format,
130 FrameAllocator::Frame(), 0, AudioFormat());
134 void CEFCapture::OnLoadEnd()
136 post_to_cef_ui_thread([this] {
138 for (const string &js : deferred_javascript) {
139 CefString script_url("<theme eval>");
141 browser->GetMainFrame()->ExecuteJavaScript(js, script_url, start_line);
143 deferred_javascript.clear();
147 #define FRAME_SIZE (8 << 20) // 8 MB.
149 void CEFCapture::configure_card()
151 if (video_frame_allocator == nullptr) {
152 owned_video_frame_allocator.reset(new MallocFrameAllocator(FRAME_SIZE, NUM_QUEUED_VIDEO_FRAMES));
153 set_video_frame_allocator(owned_video_frame_allocator.get());
157 void CEFCapture::start_bm_capture()
159 cef_app->initialize_cef();
161 CefPostTask(TID_UI, new CEFTaskAdapter([this]{
162 lock_guard<recursive_mutex> lock(browser_mutex);
164 CefBrowserSettings browser_settings;
165 browser_settings.web_security = cef_state_t::STATE_DISABLED;
166 browser_settings.webgl = cef_state_t::STATE_ENABLED;
167 browser_settings.windowless_frame_rate = max_fps;
169 CefWindowInfo window_info;
170 window_info.SetAsWindowless(0);
171 browser = CefBrowserHost::CreateBrowserSync(window_info, cef_client, start_url, browser_settings, nullptr);
172 for (function<void()> &task : deferred_tasks) {
175 deferred_tasks.clear();
179 void CEFCapture::stop_dequeue_thread()
182 lock_guard<recursive_mutex> lock(browser_mutex);
183 cef_app->close_browser(browser);
184 browser = nullptr; // Or unref_cef() will be sad.
186 cef_app->unref_cef();
189 std::map<uint32_t, VideoMode> CEFCapture::get_available_video_modes() const
194 snprintf(buf, sizeof(buf), "%ux%u", width, height);
197 mode.autodetect = false;
199 mode.height = height;
200 mode.frame_rate_num = max_fps;
201 mode.frame_rate_den = 1;
202 mode.interlaced = false;
204 return {{ 0, mode }};
207 std::map<uint32_t, std::string> CEFCapture::get_available_video_inputs() const
209 return {{ 0, "HTML video input" }};
212 std::map<uint32_t, std::string> CEFCapture::get_available_audio_inputs() const
214 return {{ 0, "Fake HTML audio input (silence)" }};
217 void CEFCapture::set_video_mode(uint32_t video_mode_id)
219 assert(video_mode_id == 0);
222 void CEFCapture::set_video_input(uint32_t video_input_id)
224 assert(video_input_id == 0);
227 void CEFCapture::set_audio_input(uint32_t audio_input_id)
229 assert(audio_input_id == 0);
232 void NageruCEFClient::OnPaint(CefRefPtr<CefBrowser> browser, PaintElementType type, const RectList &dirtyRects, const void *buffer, int width, int height)
234 parent->OnPaint(buffer, width, height);
237 bool NageruCEFClient::GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect)
239 return parent->GetViewRect(rect);
242 bool CEFCapture::GetViewRect(CefRect &rect)
244 lock_guard<mutex> lock(resolution_mutex);
245 rect = CefRect(0, 0, width, height);
249 void NageruCEFClient::OnLoadEnd(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, int httpStatusCode)