1 /* 2 * libjingle 3 * Copyright 2012, Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include "talk/examples/peerconnection/client/main_wnd.h" 29 30 #include <math.h> 31 32 #include "talk/base/common.h" 33 #include "talk/base/logging.h" 34 #include "talk/examples/peerconnection/client/defaults.h" 35 36 ATOM MainWnd::wnd_class_ = 0; 37 const wchar_t MainWnd::kClassName[] = L"WebRTC_MainWnd"; 38 39 namespace { 40 41 const char kConnecting[] = "Connecting... "; 42 const char kNoVideoStreams[] = "(no video streams either way)"; 43 const char kNoIncomingStream[] = "(no incoming video)"; 44 45 void CalculateWindowSizeForText(HWND wnd, const wchar_t* text, 46 size_t* width, size_t* height) { 47 HDC dc = ::GetDC(wnd); 48 RECT text_rc = {0}; 49 ::DrawText(dc, text, -1, &text_rc, DT_CALCRECT | DT_SINGLELINE); 50 ::ReleaseDC(wnd, dc); 51 RECT client, window; 52 ::GetClientRect(wnd, &client); 53 ::GetWindowRect(wnd, &window); 54 55 *width = text_rc.right - text_rc.left; 56 *width += (window.right - window.left) - 57 (client.right - client.left); 58 *height = text_rc.bottom - text_rc.top; 59 *height += (window.bottom - window.top) - 60 (client.bottom - client.top); 61 } 62 63 HFONT GetDefaultFont() { 64 static HFONT font = reinterpret_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT)); 65 return font; 66 } 67 68 std::string GetWindowText(HWND wnd) { 69 char text[MAX_PATH] = {0}; 70 ::GetWindowTextA(wnd, &text[0], ARRAYSIZE(text)); 71 return text; 72 } 73 74 void AddListBoxItem(HWND listbox, const std::string& str, LPARAM item_data) { 75 LRESULT index = ::SendMessageA(listbox, LB_ADDSTRING, 0, 76 reinterpret_cast<LPARAM>(str.c_str())); 77 ::SendMessageA(listbox, LB_SETITEMDATA, index, item_data); 78 } 79 80 } // namespace 81 82 MainWnd::MainWnd() 83 : ui_(CONNECT_TO_SERVER), wnd_(NULL), edit1_(NULL), edit2_(NULL), 84 label1_(NULL), label2_(NULL), button_(NULL), listbox_(NULL), 85 destroyed_(false), callback_(NULL), nested_msg_(NULL) { 86 } 87 88 MainWnd::~MainWnd() { 89 ASSERT(!IsWindow()); 90 } 91 92 bool MainWnd::Create() { 93 ASSERT(wnd_ == NULL); 94 if (!RegisterWindowClass()) 95 return false; 96 97 ui_thread_id_ = ::GetCurrentThreadId(); 98 wnd_ = ::CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, kClassName, L"WebRTC", 99 WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN, 100 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 101 NULL, NULL, GetModuleHandle(NULL), this); 102 103 ::SendMessage(wnd_, WM_SETFONT, reinterpret_cast<WPARAM>(GetDefaultFont()), 104 TRUE); 105 106 CreateChildWindows(); 107 SwitchToConnectUI(); 108 109 return wnd_ != NULL; 110 } 111 112 bool MainWnd::Destroy() { 113 BOOL ret = FALSE; 114 if (IsWindow()) { 115 ret = ::DestroyWindow(wnd_); 116 } 117 118 return ret != FALSE; 119 } 120 121 void MainWnd::RegisterObserver(MainWndCallback* callback) { 122 callback_ = callback; 123 } 124 125 bool MainWnd::IsWindow() { 126 return wnd_ && ::IsWindow(wnd_) != FALSE; 127 } 128 129 bool MainWnd::PreTranslateMessage(MSG* msg) { 130 bool ret = false; 131 if (msg->message == WM_CHAR) { 132 if (msg->wParam == VK_TAB) { 133 HandleTabbing(); 134 ret = true; 135 } else if (msg->wParam == VK_RETURN) { 136 OnDefaultAction(); 137 ret = true; 138 } else if (msg->wParam == VK_ESCAPE) { 139 if (callback_) { 140 if (ui_ == STREAMING) { 141 callback_->DisconnectFromCurrentPeer(); 142 } else { 143 callback_->DisconnectFromServer(); 144 } 145 } 146 } 147 } else if (msg->hwnd == NULL && msg->message == UI_THREAD_CALLBACK) { 148 callback_->UIThreadCallback(static_cast<int>(msg->wParam), 149 reinterpret_cast<void*>(msg->lParam)); 150 ret = true; 151 } 152 return ret; 153 } 154 155 void MainWnd::SwitchToConnectUI() { 156 ASSERT(IsWindow()); 157 LayoutPeerListUI(false); 158 ui_ = CONNECT_TO_SERVER; 159 LayoutConnectUI(true); 160 ::SetFocus(edit1_); 161 } 162 163 void MainWnd::SwitchToPeerList(const Peers& peers) { 164 LayoutConnectUI(false); 165 166 ::SendMessage(listbox_, LB_RESETCONTENT, 0, 0); 167 168 AddListBoxItem(listbox_, "List of currently connected peers:", -1); 169 Peers::const_iterator i = peers.begin(); 170 for (; i != peers.end(); ++i) 171 AddListBoxItem(listbox_, i->second.c_str(), i->first); 172 173 ui_ = LIST_PEERS; 174 LayoutPeerListUI(true); 175 ::SetFocus(listbox_); 176 } 177 178 void MainWnd::SwitchToStreamingUI() { 179 LayoutConnectUI(false); 180 LayoutPeerListUI(false); 181 ui_ = STREAMING; 182 } 183 184 void MainWnd::MessageBox(const char* caption, const char* text, bool is_error) { 185 DWORD flags = MB_OK; 186 if (is_error) 187 flags |= MB_ICONERROR; 188 189 ::MessageBoxA(handle(), text, caption, flags); 190 } 191 192 193 void MainWnd::StartLocalRenderer(webrtc::VideoTrackInterface* local_video) { 194 local_renderer_.reset(new VideoRenderer(handle(), 1, 1, local_video)); 195 } 196 197 void MainWnd::StopLocalRenderer() { 198 local_renderer_.reset(); 199 } 200 201 void MainWnd::StartRemoteRenderer(webrtc::VideoTrackInterface* remote_video) { 202 remote_renderer_.reset(new VideoRenderer(handle(), 1, 1, remote_video)); 203 } 204 205 void MainWnd::StopRemoteRenderer() { 206 remote_renderer_.reset(); 207 } 208 209 void MainWnd::QueueUIThreadCallback(int msg_id, void* data) { 210 ::PostThreadMessage(ui_thread_id_, UI_THREAD_CALLBACK, 211 static_cast<WPARAM>(msg_id), reinterpret_cast<LPARAM>(data)); 212 } 213 214 void MainWnd::OnPaint() { 215 PAINTSTRUCT ps; 216 ::BeginPaint(handle(), &ps); 217 218 RECT rc; 219 ::GetClientRect(handle(), &rc); 220 221 VideoRenderer* local_renderer = local_renderer_.get(); 222 VideoRenderer* remote_renderer = remote_renderer_.get(); 223 if (ui_ == STREAMING && remote_renderer && local_renderer) { 224 AutoLock<VideoRenderer> local_lock(local_renderer); 225 AutoLock<VideoRenderer> remote_lock(remote_renderer); 226 227 const BITMAPINFO& bmi = remote_renderer->bmi(); 228 int height = abs(bmi.bmiHeader.biHeight); 229 int width = bmi.bmiHeader.biWidth; 230 231 const uint8* image = remote_renderer->image(); 232 if (image != NULL) { 233 HDC dc_mem = ::CreateCompatibleDC(ps.hdc); 234 ::SetStretchBltMode(dc_mem, HALFTONE); 235 236 // Set the map mode so that the ratio will be maintained for us. 237 HDC all_dc[] = { ps.hdc, dc_mem }; 238 for (int i = 0; i < ARRAY_SIZE(all_dc); ++i) { 239 SetMapMode(all_dc[i], MM_ISOTROPIC); 240 SetWindowExtEx(all_dc[i], width, height, NULL); 241 SetViewportExtEx(all_dc[i], rc.right, rc.bottom, NULL); 242 } 243 244 HBITMAP bmp_mem = ::CreateCompatibleBitmap(ps.hdc, rc.right, rc.bottom); 245 HGDIOBJ bmp_old = ::SelectObject(dc_mem, bmp_mem); 246 247 POINT logical_area = { rc.right, rc.bottom }; 248 DPtoLP(ps.hdc, &logical_area, 1); 249 250 HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0)); 251 RECT logical_rect = {0, 0, logical_area.x, logical_area.y }; 252 ::FillRect(dc_mem, &logical_rect, brush); 253 ::DeleteObject(brush); 254 255 int x = (logical_area.x / 2) - (width / 2); 256 int y = (logical_area.y / 2) - (height / 2); 257 258 StretchDIBits(dc_mem, x, y, width, height, 259 0, 0, width, height, image, &bmi, DIB_RGB_COLORS, SRCCOPY); 260 261 if ((rc.right - rc.left) > 200 && (rc.bottom - rc.top) > 200) { 262 const BITMAPINFO& bmi = local_renderer->bmi(); 263 image = local_renderer->image(); 264 int thumb_width = bmi.bmiHeader.biWidth / 4; 265 int thumb_height = abs(bmi.bmiHeader.biHeight) / 4; 266 StretchDIBits(dc_mem, 267 logical_area.x - thumb_width - 10, 268 logical_area.y - thumb_height - 10, 269 thumb_width, thumb_height, 270 0, 0, bmi.bmiHeader.biWidth, -bmi.bmiHeader.biHeight, 271 image, &bmi, DIB_RGB_COLORS, SRCCOPY); 272 } 273 274 BitBlt(ps.hdc, 0, 0, logical_area.x, logical_area.y, 275 dc_mem, 0, 0, SRCCOPY); 276 277 // Cleanup. 278 ::SelectObject(dc_mem, bmp_old); 279 ::DeleteObject(bmp_mem); 280 ::DeleteDC(dc_mem); 281 } else { 282 // We're still waiting for the video stream to be initialized. 283 HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0)); 284 ::FillRect(ps.hdc, &rc, brush); 285 ::DeleteObject(brush); 286 287 HGDIOBJ old_font = ::SelectObject(ps.hdc, GetDefaultFont()); 288 ::SetTextColor(ps.hdc, RGB(0xff, 0xff, 0xff)); 289 ::SetBkMode(ps.hdc, TRANSPARENT); 290 291 std::string text(kConnecting); 292 if (!local_renderer->image()) { 293 text += kNoVideoStreams; 294 } else { 295 text += kNoIncomingStream; 296 } 297 ::DrawTextA(ps.hdc, text.c_str(), -1, &rc, 298 DT_SINGLELINE | DT_CENTER | DT_VCENTER); 299 ::SelectObject(ps.hdc, old_font); 300 } 301 } else { 302 HBRUSH brush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW)); 303 ::FillRect(ps.hdc, &rc, brush); 304 ::DeleteObject(brush); 305 } 306 307 ::EndPaint(handle(), &ps); 308 } 309 310 void MainWnd::OnDestroyed() { 311 PostQuitMessage(0); 312 } 313 314 void MainWnd::OnDefaultAction() { 315 if (!callback_) 316 return; 317 if (ui_ == CONNECT_TO_SERVER) { 318 std::string server(GetWindowText(edit1_)); 319 std::string port_str(GetWindowText(edit2_)); 320 int port = port_str.length() ? atoi(port_str.c_str()) : 0; 321 callback_->StartLogin(server, port); 322 } else if (ui_ == LIST_PEERS) { 323 LRESULT sel = ::SendMessage(listbox_, LB_GETCURSEL, 0, 0); 324 if (sel != LB_ERR) { 325 LRESULT peer_id = ::SendMessage(listbox_, LB_GETITEMDATA, sel, 0); 326 if (peer_id != -1 && callback_) { 327 callback_->ConnectToPeer(peer_id); 328 } 329 } 330 } else { 331 MessageBoxA(wnd_, "OK!", "Yeah", MB_OK); 332 } 333 } 334 335 bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result) { 336 switch (msg) { 337 case WM_ERASEBKGND: 338 *result = TRUE; 339 return true; 340 341 case WM_PAINT: 342 OnPaint(); 343 return true; 344 345 case WM_SETFOCUS: 346 if (ui_ == CONNECT_TO_SERVER) { 347 SetFocus(edit1_); 348 } else if (ui_ == LIST_PEERS) { 349 SetFocus(listbox_); 350 } 351 return true; 352 353 case WM_SIZE: 354 if (ui_ == CONNECT_TO_SERVER) { 355 LayoutConnectUI(true); 356 } else if (ui_ == LIST_PEERS) { 357 LayoutPeerListUI(true); 358 } 359 break; 360 361 case WM_CTLCOLORSTATIC: 362 *result = reinterpret_cast<LRESULT>(GetSysColorBrush(COLOR_WINDOW)); 363 return true; 364 365 case WM_COMMAND: 366 if (button_ == reinterpret_cast<HWND>(lp)) { 367 if (BN_CLICKED == HIWORD(wp)) 368 OnDefaultAction(); 369 } else if (listbox_ == reinterpret_cast<HWND>(lp)) { 370 if (LBN_DBLCLK == HIWORD(wp)) { 371 OnDefaultAction(); 372 } 373 } 374 return true; 375 376 case WM_CLOSE: 377 if (callback_) 378 callback_->Close(); 379 break; 380 } 381 return false; 382 } 383 384 // static 385 LRESULT CALLBACK MainWnd::WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { 386 MainWnd* me = reinterpret_cast<MainWnd*>( 387 ::GetWindowLongPtr(hwnd, GWLP_USERDATA)); 388 if (!me && WM_CREATE == msg) { 389 CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lp); 390 me = reinterpret_cast<MainWnd*>(cs->lpCreateParams); 391 me->wnd_ = hwnd; 392 ::SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(me)); 393 } 394 395 LRESULT result = 0; 396 if (me) { 397 void* prev_nested_msg = me->nested_msg_; 398 me->nested_msg_ = &msg; 399 400 bool handled = me->OnMessage(msg, wp, lp, &result); 401 if (WM_NCDESTROY == msg) { 402 me->destroyed_ = true; 403 } else if (!handled) { 404 result = ::DefWindowProc(hwnd, msg, wp, lp); 405 } 406 407 if (me->destroyed_ && prev_nested_msg == NULL) { 408 me->OnDestroyed(); 409 me->wnd_ = NULL; 410 me->destroyed_ = false; 411 } 412 413 me->nested_msg_ = prev_nested_msg; 414 } else { 415 result = ::DefWindowProc(hwnd, msg, wp, lp); 416 } 417 418 return result; 419 } 420 421 // static 422 bool MainWnd::RegisterWindowClass() { 423 if (wnd_class_) 424 return true; 425 426 WNDCLASSEX wcex = { sizeof(WNDCLASSEX) }; 427 wcex.style = CS_DBLCLKS; 428 wcex.hInstance = GetModuleHandle(NULL); 429 wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1); 430 wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW); 431 wcex.lpfnWndProc = &WndProc; 432 wcex.lpszClassName = kClassName; 433 wnd_class_ = ::RegisterClassEx(&wcex); 434 ASSERT(wnd_class_ != 0); 435 return wnd_class_ != 0; 436 } 437 438 void MainWnd::CreateChildWindow(HWND* wnd, MainWnd::ChildWindowID id, 439 const wchar_t* class_name, DWORD control_style, 440 DWORD ex_style) { 441 if (::IsWindow(*wnd)) 442 return; 443 444 // Child windows are invisible at first, and shown after being resized. 445 DWORD style = WS_CHILD | control_style; 446 *wnd = ::CreateWindowEx(ex_style, class_name, L"", style, 447 100, 100, 100, 100, wnd_, 448 reinterpret_cast<HMENU>(id), 449 GetModuleHandle(NULL), NULL); 450 ASSERT(::IsWindow(*wnd) != FALSE); 451 ::SendMessage(*wnd, WM_SETFONT, reinterpret_cast<WPARAM>(GetDefaultFont()), 452 TRUE); 453 } 454 455 void MainWnd::CreateChildWindows() { 456 // Create the child windows in tab order. 457 CreateChildWindow(&label1_, LABEL1_ID, L"Static", ES_CENTER | ES_READONLY, 0); 458 CreateChildWindow(&edit1_, EDIT_ID, L"Edit", 459 ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, WS_EX_CLIENTEDGE); 460 CreateChildWindow(&label2_, LABEL2_ID, L"Static", ES_CENTER | ES_READONLY, 0); 461 CreateChildWindow(&edit2_, EDIT_ID, L"Edit", 462 ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, WS_EX_CLIENTEDGE); 463 CreateChildWindow(&button_, BUTTON_ID, L"Button", BS_CENTER | WS_TABSTOP, 0); 464 465 CreateChildWindow(&listbox_, LISTBOX_ID, L"ListBox", 466 LBS_HASSTRINGS | LBS_NOTIFY, WS_EX_CLIENTEDGE); 467 468 ::SetWindowTextA(edit1_, GetDefaultServerName().c_str()); 469 ::SetWindowTextA(edit2_, "8888"); 470 } 471 472 void MainWnd::LayoutConnectUI(bool show) { 473 struct Windows { 474 HWND wnd; 475 const wchar_t* text; 476 size_t width; 477 size_t height; 478 } windows[] = { 479 { label1_, L"Server" }, 480 { edit1_, L"XXXyyyYYYgggXXXyyyYYYggg" }, 481 { label2_, L":" }, 482 { edit2_, L"XyXyX" }, 483 { button_, L"Connect" }, 484 }; 485 486 if (show) { 487 const size_t kSeparator = 5; 488 size_t total_width = (ARRAYSIZE(windows) - 1) * kSeparator; 489 490 for (size_t i = 0; i < ARRAYSIZE(windows); ++i) { 491 CalculateWindowSizeForText(windows[i].wnd, windows[i].text, 492 &windows[i].width, &windows[i].height); 493 total_width += windows[i].width; 494 } 495 496 RECT rc; 497 ::GetClientRect(wnd_, &rc); 498 size_t x = (rc.right / 2) - (total_width / 2); 499 size_t y = rc.bottom / 2; 500 for (size_t i = 0; i < ARRAYSIZE(windows); ++i) { 501 size_t top = y - (windows[i].height / 2); 502 ::MoveWindow(windows[i].wnd, static_cast<int>(x), static_cast<int>(top), 503 static_cast<int>(windows[i].width), 504 static_cast<int>(windows[i].height), 505 TRUE); 506 x += kSeparator + windows[i].width; 507 if (windows[i].text[0] != 'X') 508 ::SetWindowText(windows[i].wnd, windows[i].text); 509 ::ShowWindow(windows[i].wnd, SW_SHOWNA); 510 } 511 } else { 512 for (size_t i = 0; i < ARRAYSIZE(windows); ++i) { 513 ::ShowWindow(windows[i].wnd, SW_HIDE); 514 } 515 } 516 } 517 518 void MainWnd::LayoutPeerListUI(bool show) { 519 if (show) { 520 RECT rc; 521 ::GetClientRect(wnd_, &rc); 522 ::MoveWindow(listbox_, 0, 0, rc.right, rc.bottom, TRUE); 523 ::ShowWindow(listbox_, SW_SHOWNA); 524 } else { 525 ::ShowWindow(listbox_, SW_HIDE); 526 InvalidateRect(wnd_, NULL, TRUE); 527 } 528 } 529 530 void MainWnd::HandleTabbing() { 531 bool shift = ((::GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0); 532 UINT next_cmd = shift ? GW_HWNDPREV : GW_HWNDNEXT; 533 UINT loop_around_cmd = shift ? GW_HWNDLAST : GW_HWNDFIRST; 534 HWND focus = GetFocus(), next; 535 do { 536 next = ::GetWindow(focus, next_cmd); 537 if (IsWindowVisible(next) && 538 (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP)) { 539 break; 540 } 541 542 if (!next) { 543 next = ::GetWindow(focus, loop_around_cmd); 544 if (IsWindowVisible(next) && 545 (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP)) { 546 break; 547 } 548 } 549 focus = next; 550 } while (true); 551 ::SetFocus(next); 552 } 553 554 // 555 // MainWnd::VideoRenderer 556 // 557 558 MainWnd::VideoRenderer::VideoRenderer( 559 HWND wnd, int width, int height, 560 webrtc::VideoTrackInterface* track_to_render) 561 : wnd_(wnd), rendered_track_(track_to_render) { 562 ::InitializeCriticalSection(&buffer_lock_); 563 ZeroMemory(&bmi_, sizeof(bmi_)); 564 bmi_.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); 565 bmi_.bmiHeader.biPlanes = 1; 566 bmi_.bmiHeader.biBitCount = 32; 567 bmi_.bmiHeader.biCompression = BI_RGB; 568 bmi_.bmiHeader.biWidth = width; 569 bmi_.bmiHeader.biHeight = -height; 570 bmi_.bmiHeader.biSizeImage = width * height * 571 (bmi_.bmiHeader.biBitCount >> 3); 572 rendered_track_->AddRenderer(this); 573 } 574 575 MainWnd::VideoRenderer::~VideoRenderer() { 576 rendered_track_->RemoveRenderer(this); 577 ::DeleteCriticalSection(&buffer_lock_); 578 } 579 580 void MainWnd::VideoRenderer::SetSize(int width, int height) { 581 AutoLock<VideoRenderer> lock(this); 582 583 bmi_.bmiHeader.biWidth = width; 584 bmi_.bmiHeader.biHeight = -height; 585 bmi_.bmiHeader.biSizeImage = width * height * 586 (bmi_.bmiHeader.biBitCount >> 3); 587 image_.reset(new uint8[bmi_.bmiHeader.biSizeImage]); 588 } 589 590 void MainWnd::VideoRenderer::RenderFrame(const cricket::VideoFrame* frame) { 591 if (!frame) 592 return; 593 594 { 595 AutoLock<VideoRenderer> lock(this); 596 597 ASSERT(image_.get() != NULL); 598 frame->ConvertToRgbBuffer(cricket::FOURCC_ARGB, 599 image_.get(), 600 bmi_.bmiHeader.biSizeImage, 601 bmi_.bmiHeader.biWidth * 602 bmi_.bmiHeader.biBitCount / 8); 603 } 604 InvalidateRect(wnd_, NULL, TRUE); 605 } 606