13 #include "cef_capture.h"
14 #include "base/cef_logging.h"
15 #include "nageru_cef_app.h"
16 #include "nageru/defs.h"
19 #include <cef_browser.h>
20 #include <cef_frame.h>
23 #include "bmusb/bmusb.h"
26 using namespace std::chrono;
27 using namespace bmusb;
29 extern CefRefPtr<NageruCefApp> cef_app;
31 CEFCapture::CEFCapture(const string &url, unsigned width, unsigned height)
32 : cef_client(new NageruCEFClient(this)),
38 snprintf(buf, sizeof(buf), "CEF card %d", card_index + 1);
42 CEFCapture::~CEFCapture()
44 if (has_dequeue_callbacks) {
45 dequeue_cleanup_callback();
49 void CEFCapture::post_to_cef_ui_thread(std::function<void()> &&func, int64_t delay_ms)
51 lock_guard<recursive_mutex> lock(browser_mutex);
52 if (browser != nullptr) {
54 CefPostTask(TID_UI, new CEFTaskAdapter(std::move(func)));
56 CefPostDelayedTask(TID_UI, new CEFTaskAdapter(std::move(func)), delay_ms);
59 deferred_tasks.push_back(std::move(func));
63 void CEFCapture::set_url(const string &url)
65 post_to_cef_ui_thread([this, url] {
67 browser->GetMainFrame()->LoadURL(url);
71 void CEFCapture::reload()
73 post_to_cef_ui_thread([this] {
79 void CEFCapture::set_max_fps(int max_fps)
81 post_to_cef_ui_thread([this, max_fps] {
82 browser->GetHost()->SetWindowlessFrameRate(max_fps);
83 this->max_fps = max_fps;
87 void CEFCapture::execute_javascript_async(const string &js)
89 post_to_cef_ui_thread([this, js] {
91 CefString script_url("<theme eval>");
93 browser->GetMainFrame()->ExecuteJavaScript(js, script_url, start_line);
95 deferred_javascript.push_back(js);
100 void CEFCapture::resize(unsigned width, unsigned height)
102 lock_guard<mutex> lock(resolution_mutex);
104 this->height = height;
107 void CEFCapture::request_new_frame(bool ignore_if_locked)
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.
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);
129 void CEFCapture::OnPaint(const void *buffer, int width, int height)
131 steady_clock::time_point timestamp = steady_clock::now();
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;
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);
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());
162 void CEFCapture::OnLoadEnd()
164 post_to_cef_ui_thread([this] {
166 for (const string &js : deferred_javascript) {
167 CefString script_url("<theme eval>");
169 browser->GetMainFrame()->ExecuteJavaScript(js, script_url, start_line);
171 deferred_javascript.clear();
175 void CEFCapture::configure_card()
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());
183 void CEFCapture::start_bm_capture()
185 cef_app->initialize_cef();
187 CefPostTask(TID_UI, new CEFTaskAdapter([this]{
188 lock_guard<recursive_mutex> lock(browser_mutex);
190 CefBrowserSettings browser_settings;
191 browser_settings.webgl = cef_state_t::STATE_ENABLED;
192 browser_settings.windowless_frame_rate = max_fps;
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) {
200 deferred_tasks.clear();
204 void CEFCapture::stop_dequeue_thread()
207 lock_guard<recursive_mutex> lock(browser_mutex);
208 cef_app->close_browser(browser);
209 browser = nullptr; // Or unref_cef() will be sad.
211 cef_app->unref_cef();
214 std::map<uint32_t, VideoMode> CEFCapture::get_available_video_modes() const
219 snprintf(buf, sizeof(buf), "%ux%u", width, height);
222 mode.autodetect = false;
224 mode.height = height;
225 mode.frame_rate_num = max_fps;
226 mode.frame_rate_den = 1;
227 mode.interlaced = false;
229 return {{ 0, mode }};
232 std::map<uint32_t, std::string> CEFCapture::get_available_video_inputs() const
234 return {{ 0, "HTML video input" }};
237 std::map<uint32_t, std::string> CEFCapture::get_available_audio_inputs() const
239 return {{ 0, "Fake HTML audio input (silence)" }};
242 void CEFCapture::set_video_mode(uint32_t video_mode_id)
244 assert(video_mode_id == 0);
247 void CEFCapture::set_video_input(uint32_t video_input_id)
249 assert(video_input_id == 0);
252 void CEFCapture::set_audio_input(uint32_t audio_input_id)
254 assert(audio_input_id == 0);
257 void NageruCEFClient::OnPaint(CefRefPtr<CefBrowser> browser, PaintElementType type, const RectList &dirtyRects, const void *buffer, int width, int height)
259 parent->OnPaint(buffer, width, height);
262 void NageruCEFClient::GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect)
264 parent->GetViewRect(rect);
267 void CEFCapture::GetViewRect(CefRect &rect)
269 lock_guard<mutex> lock(resolution_mutex);
270 rect = CefRect(0, 0, width, height);
273 void NageruCEFClient::OnLoadEnd(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, int httpStatusCode)