1// libjingle 2// Copyright 2004 Google Inc. 3// 4// Redistribution and use in source and binary forms, with or without 5// modification, are permitted provided that the following conditions are met: 6// 7// 1. Redistributions of source code must retain the above copyright notice, 8// this list of conditions and the following disclaimer. 9// 2. Redistributions in binary form must reproduce the above copyright notice, 10// this list of conditions and the following disclaimer in the documentation 11// and/or other materials provided with the distribution. 12// 3. The name of the author may not be used to endorse or promote products 13// derived from this software without specific prior written permission. 14// 15// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 16// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 17// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 18// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 21// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 22// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 23// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 24// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25// 26// Implementation of GdiVideoRenderer on Windows 27 28#ifdef WIN32 29 30#include "talk/media/devices/gdivideorenderer.h" 31 32#include "talk/media/base/videocommon.h" 33#include "talk/media/base/videoframe.h" 34#include "webrtc/base/scoped_ptr.h" 35#include "webrtc/base/thread.h" 36#include "webrtc/base/win32window.h" 37 38namespace cricket { 39 40///////////////////////////////////////////////////////////////////////////// 41// Definition of private class VideoWindow. We use a worker thread to manage 42// the window. 43///////////////////////////////////////////////////////////////////////////// 44class GdiVideoRenderer::VideoWindow : public rtc::Win32Window { 45 public: 46 VideoWindow(int x, int y, int width, int height); 47 virtual ~VideoWindow(); 48 49 // Called when the video size changes. If it is called the first time, we 50 // create and start the thread. Otherwise, we send kSetSizeMsg to the thread. 51 // Context: non-worker thread. 52 bool SetSize(int width, int height); 53 54 // Called when a new frame is available. Upon this call, we send 55 // kRenderFrameMsg to the window thread. Context: non-worker thread. It may be 56 // better to pass RGB bytes to VideoWindow. However, we pass VideoFrame to put 57 // all the thread synchronization within VideoWindow. 58 bool RenderFrame(const VideoFrame* frame); 59 60 protected: 61 // Override virtual method of rtc::Win32Window. Context: worker Thread. 62 virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, 63 LRESULT& result); 64 65 private: 66 enum { kSetSizeMsg = WM_USER, kRenderFrameMsg}; 67 68 class WindowThread : public rtc::Thread { 69 public: 70 explicit WindowThread(VideoWindow* window) : window_(window) {} 71 72 virtual ~WindowThread() { 73 Stop(); 74 } 75 76 // Override virtual method of rtc::Thread. Context: worker Thread. 77 virtual void Run() { 78 // Initialize the window 79 if (!window_ || !window_->Initialize()) { 80 return; 81 } 82 // Run the message loop 83 MSG msg; 84 while (GetMessage(&msg, NULL, 0, 0) > 0) { 85 TranslateMessage(&msg); 86 DispatchMessage(&msg); 87 } 88 } 89 90 private: 91 VideoWindow* window_; 92 }; 93 94 // Context: worker Thread. 95 bool Initialize(); 96 void OnPaint(); 97 void OnSize(int width, int height, bool frame_changed); 98 void OnRenderFrame(const VideoFrame* frame); 99 100 BITMAPINFO bmi_; 101 rtc::scoped_ptr<uint8[]> image_; 102 rtc::scoped_ptr<WindowThread> window_thread_; 103 // The initial position of the window. 104 int initial_x_; 105 int initial_y_; 106}; 107 108///////////////////////////////////////////////////////////////////////////// 109// Implementation of class VideoWindow 110///////////////////////////////////////////////////////////////////////////// 111GdiVideoRenderer::VideoWindow::VideoWindow( 112 int x, int y, int width, int height) 113 : initial_x_(x), 114 initial_y_(y) { 115 memset(&bmi_.bmiHeader, 0, sizeof(bmi_.bmiHeader)); 116 bmi_.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); 117 bmi_.bmiHeader.biPlanes = 1; 118 bmi_.bmiHeader.biBitCount = 32; 119 bmi_.bmiHeader.biCompression = BI_RGB; 120 bmi_.bmiHeader.biWidth = width; 121 bmi_.bmiHeader.biHeight = -height; 122 bmi_.bmiHeader.biSizeImage = width * height * 4; 123 124 image_.reset(new uint8[bmi_.bmiHeader.biSizeImage]); 125} 126 127GdiVideoRenderer::VideoWindow::~VideoWindow() { 128 // Context: caller Thread. We cannot call Destroy() since the window was 129 // created by another thread. Instead, we send WM_CLOSE message. 130 if (handle()) { 131 SendMessage(handle(), WM_CLOSE, 0, 0); 132 } 133} 134 135bool GdiVideoRenderer::VideoWindow::SetSize(int width, int height) { 136 if (!window_thread_.get()) { 137 // Create and start the window thread. 138 window_thread_.reset(new WindowThread(this)); 139 return window_thread_->Start(); 140 } else if (width != bmi_.bmiHeader.biWidth || 141 height != -bmi_.bmiHeader.biHeight) { 142 SendMessage(handle(), kSetSizeMsg, 0, MAKELPARAM(width, height)); 143 } 144 return true; 145} 146 147bool GdiVideoRenderer::VideoWindow::RenderFrame(const VideoFrame* frame) { 148 if (!handle()) { 149 return false; 150 } 151 152 SendMessage(handle(), kRenderFrameMsg, reinterpret_cast<WPARAM>(frame), 0); 153 return true; 154} 155 156bool GdiVideoRenderer::VideoWindow::OnMessage(UINT uMsg, WPARAM wParam, 157 LPARAM lParam, LRESULT& result) { 158 switch (uMsg) { 159 case WM_PAINT: 160 OnPaint(); 161 return true; 162 163 case WM_DESTROY: 164 PostQuitMessage(0); // post WM_QUIT to end the message loop in Run() 165 return false; 166 167 case WM_SIZE: // The window UI was resized. 168 OnSize(LOWORD(lParam), HIWORD(lParam), false); 169 return true; 170 171 case kSetSizeMsg: // The video resolution changed. 172 OnSize(LOWORD(lParam), HIWORD(lParam), true); 173 return true; 174 175 case kRenderFrameMsg: 176 OnRenderFrame(reinterpret_cast<const VideoFrame*>(wParam)); 177 return true; 178 } 179 return false; 180} 181 182bool GdiVideoRenderer::VideoWindow::Initialize() { 183 if (!rtc::Win32Window::Create( 184 NULL, L"Video Renderer", 185 WS_OVERLAPPEDWINDOW | WS_SIZEBOX, 186 WS_EX_APPWINDOW, 187 initial_x_, initial_y_, 188 bmi_.bmiHeader.biWidth, -bmi_.bmiHeader.biHeight)) { 189 return false; 190 } 191 OnSize(bmi_.bmiHeader.biWidth, -bmi_.bmiHeader.biHeight, false); 192 return true; 193} 194 195void GdiVideoRenderer::VideoWindow::OnPaint() { 196 RECT rcClient; 197 GetClientRect(handle(), &rcClient); 198 PAINTSTRUCT ps; 199 HDC hdc = BeginPaint(handle(), &ps); 200 StretchDIBits(hdc, 201 0, 0, rcClient.right, rcClient.bottom, // destination rect 202 0, 0, bmi_.bmiHeader.biWidth, -bmi_.bmiHeader.biHeight, // source rect 203 image_.get(), &bmi_, DIB_RGB_COLORS, SRCCOPY); 204 EndPaint(handle(), &ps); 205} 206 207void GdiVideoRenderer::VideoWindow::OnSize(int width, int height, 208 bool frame_changed) { 209 // Get window and client sizes 210 RECT rcClient, rcWindow; 211 GetClientRect(handle(), &rcClient); 212 GetWindowRect(handle(), &rcWindow); 213 214 // Find offset between window size and client size 215 POINT ptDiff; 216 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 217 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 218 219 // Resize client 220 MoveWindow(handle(), rcWindow.left, rcWindow.top, 221 width + ptDiff.x, height + ptDiff.y, false); 222 UpdateWindow(handle()); 223 ShowWindow(handle(), SW_SHOW); 224 225 if (frame_changed && (width != bmi_.bmiHeader.biWidth || 226 height != -bmi_.bmiHeader.biHeight)) { 227 // Update the bmi and image buffer 228 bmi_.bmiHeader.biWidth = width; 229 bmi_.bmiHeader.biHeight = -height; 230 bmi_.bmiHeader.biSizeImage = width * height * 4; 231 image_.reset(new uint8[bmi_.bmiHeader.biSizeImage]); 232 } 233} 234 235void GdiVideoRenderer::VideoWindow::OnRenderFrame(const VideoFrame* frame) { 236 if (!frame) { 237 return; 238 } 239 // Convert frame to ARGB format, which is accepted by GDI 240 frame->ConvertToRgbBuffer(cricket::FOURCC_ARGB, image_.get(), 241 bmi_.bmiHeader.biSizeImage, 242 bmi_.bmiHeader.biWidth * 4); 243 InvalidateRect(handle(), 0, 0); 244} 245 246///////////////////////////////////////////////////////////////////////////// 247// Implementation of class GdiVideoRenderer 248///////////////////////////////////////////////////////////////////////////// 249GdiVideoRenderer::GdiVideoRenderer(int x, int y) 250 : initial_x_(x), 251 initial_y_(y) { 252} 253GdiVideoRenderer::~GdiVideoRenderer() {} 254 255bool GdiVideoRenderer::SetSize(int width, int height, int reserved) { 256 if (!window_.get()) { // Create the window for the first frame 257 window_.reset(new VideoWindow(initial_x_, initial_y_, width, height)); 258 } 259 return window_->SetSize(width, height); 260} 261 262bool GdiVideoRenderer::RenderFrame(const VideoFrame* frame) { 263 if (!frame || !window_.get()) { 264 return false; 265 } 266 return window_->RenderFrame(frame); 267} 268 269} // namespace cricket 270#endif // WIN32 271