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