Home | History | Annotate | Download | only in devices
      1 /*
      2  * libjingle
      3  * Copyright 2004 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 // Implementation of GdiVideoRenderer on Windows
     29 
     30 #ifdef WIN32
     31 
     32 #include "talk/media/devices/gdivideorenderer.h"
     33 
     34 #include "talk/media/base/videocommon.h"
     35 #include "talk/media/base/videoframe.h"
     36 #include "webrtc/base/scoped_ptr.h"
     37 #include "webrtc/base/thread.h"
     38 #include "webrtc/base/win32window.h"
     39 
     40 namespace cricket {
     41 
     42 /////////////////////////////////////////////////////////////////////////////
     43 // Definition of private class VideoWindow. We use a worker thread to manage
     44 // the window.
     45 /////////////////////////////////////////////////////////////////////////////
     46 class GdiVideoRenderer::VideoWindow : public rtc::Win32Window {
     47  public:
     48   VideoWindow(int x, int y, int width, int height);
     49   virtual ~VideoWindow();
     50 
     51   // Called when the video size changes. If it is called the first time, we
     52   // create and start the thread. Otherwise, we send kSetSizeMsg to the thread.
     53   // Context: non-worker thread.
     54   bool SetSize(int width, int height);
     55 
     56   // Called when a new frame is available. Upon this call, we send
     57   // kRenderFrameMsg to the window thread. Context: non-worker thread. It may be
     58   // better to pass RGB bytes to VideoWindow. However, we pass VideoFrame to put
     59   // all the thread synchronization within VideoWindow.
     60   bool RenderFrame(const VideoFrame* frame);
     61 
     62  protected:
     63   // Override virtual method of rtc::Win32Window. Context: worker Thread.
     64   virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
     65                          LRESULT& result);
     66 
     67  private:
     68   enum { kSetSizeMsg = WM_USER, kRenderFrameMsg};
     69 
     70   class WindowThread : public rtc::Thread {
     71    public:
     72     explicit WindowThread(VideoWindow* window) : window_(window) {}
     73 
     74     virtual ~WindowThread() {
     75       Stop();
     76     }
     77 
     78     // Override virtual method of rtc::Thread. Context: worker Thread.
     79     virtual void Run() {
     80       // Initialize the window
     81       if (!window_ || !window_->Initialize()) {
     82         return;
     83       }
     84       // Run the message loop
     85       MSG msg;
     86       while (GetMessage(&msg, NULL, 0, 0) > 0) {
     87         TranslateMessage(&msg);
     88         DispatchMessage(&msg);
     89       }
     90     }
     91 
     92   private:
     93     VideoWindow* window_;
     94   };
     95 
     96   // Context: worker Thread.
     97   bool Initialize();
     98   void OnPaint();
     99   void OnSize(int width, int height, bool frame_changed);
    100   void OnRenderFrame(const VideoFrame* frame);
    101 
    102   BITMAPINFO bmi_;
    103   rtc::scoped_ptr<uint8_t[]> image_;
    104   rtc::scoped_ptr<WindowThread> window_thread_;
    105   // The initial position of the window.
    106   int initial_x_;
    107   int initial_y_;
    108 };
    109 
    110 /////////////////////////////////////////////////////////////////////////////
    111 // Implementation of class VideoWindow
    112 /////////////////////////////////////////////////////////////////////////////
    113 GdiVideoRenderer::VideoWindow::VideoWindow(
    114     int x, int y, int width, int height)
    115     : initial_x_(x),
    116       initial_y_(y) {
    117   memset(&bmi_.bmiHeader, 0, sizeof(bmi_.bmiHeader));
    118   bmi_.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    119   bmi_.bmiHeader.biPlanes = 1;
    120   bmi_.bmiHeader.biBitCount = 32;
    121   bmi_.bmiHeader.biCompression = BI_RGB;
    122   bmi_.bmiHeader.biWidth = width;
    123   bmi_.bmiHeader.biHeight = -height;
    124   bmi_.bmiHeader.biSizeImage = width * height * 4;
    125 
    126   image_.reset(new uint8_t[bmi_.bmiHeader.biSizeImage]);
    127 }
    128 
    129 GdiVideoRenderer::VideoWindow::~VideoWindow() {
    130   // Context: caller Thread. We cannot call Destroy() since the window was
    131   // created by another thread. Instead, we send WM_CLOSE message.
    132   if (handle()) {
    133     SendMessage(handle(), WM_CLOSE, 0, 0);
    134   }
    135 }
    136 
    137 bool GdiVideoRenderer::VideoWindow::SetSize(int width, int height) {
    138   if (!window_thread_.get()) {
    139     // Create and start the window thread.
    140     window_thread_.reset(new WindowThread(this));
    141     return window_thread_->Start();
    142   } else if (width != bmi_.bmiHeader.biWidth ||
    143       height != -bmi_.bmiHeader.biHeight) {
    144     SendMessage(handle(), kSetSizeMsg, 0, MAKELPARAM(width, height));
    145   }
    146   return true;
    147 }
    148 
    149 bool GdiVideoRenderer::VideoWindow::RenderFrame(const VideoFrame* video_frame) {
    150   if (!handle()) {
    151     return false;
    152   }
    153 
    154   const VideoFrame* frame = video_frame->GetCopyWithRotationApplied();
    155 
    156   if (!SetSize(static_cast<int>(frame->GetWidth()),
    157                static_cast<int>(frame->GetHeight()))) {
    158     return false;
    159   }
    160 
    161   SendMessage(handle(), kRenderFrameMsg, reinterpret_cast<WPARAM>(frame), 0);
    162   return true;
    163 }
    164 
    165 bool GdiVideoRenderer::VideoWindow::OnMessage(UINT uMsg, WPARAM wParam,
    166                                               LPARAM lParam, LRESULT& result) {
    167   switch (uMsg) {
    168     case WM_PAINT:
    169       OnPaint();
    170       return true;
    171 
    172     case WM_DESTROY:
    173       PostQuitMessage(0);  // post WM_QUIT to end the message loop in Run()
    174       return false;
    175 
    176     case WM_SIZE:  // The window UI was resized.
    177       OnSize(LOWORD(lParam), HIWORD(lParam), false);
    178       return true;
    179 
    180     case kSetSizeMsg:  // The video resolution changed.
    181       OnSize(LOWORD(lParam), HIWORD(lParam), true);
    182       return true;
    183 
    184     case kRenderFrameMsg:
    185       OnRenderFrame(reinterpret_cast<const VideoFrame*>(wParam));
    186       return true;
    187   }
    188   return false;
    189 }
    190 
    191 bool GdiVideoRenderer::VideoWindow::Initialize() {
    192   if (!rtc::Win32Window::Create(
    193       NULL, L"Video Renderer",
    194       WS_OVERLAPPEDWINDOW | WS_SIZEBOX,
    195       WS_EX_APPWINDOW,
    196       initial_x_, initial_y_,
    197       bmi_.bmiHeader.biWidth, -bmi_.bmiHeader.biHeight)) {
    198         return false;
    199   }
    200   OnSize(bmi_.bmiHeader.biWidth, -bmi_.bmiHeader.biHeight, false);
    201   return true;
    202 }
    203 
    204 void GdiVideoRenderer::VideoWindow::OnPaint() {
    205   RECT rcClient;
    206   GetClientRect(handle(), &rcClient);
    207   PAINTSTRUCT ps;
    208   HDC hdc = BeginPaint(handle(), &ps);
    209   StretchDIBits(hdc,
    210     0, 0, rcClient.right, rcClient.bottom,  // destination rect
    211     0, 0, bmi_.bmiHeader.biWidth, -bmi_.bmiHeader.biHeight,  // source rect
    212     image_.get(), &bmi_, DIB_RGB_COLORS, SRCCOPY);
    213   EndPaint(handle(), &ps);
    214 }
    215 
    216 void GdiVideoRenderer::VideoWindow::OnSize(int width, int height,
    217                                            bool frame_changed) {
    218   // Get window and client sizes
    219   RECT rcClient, rcWindow;
    220   GetClientRect(handle(), &rcClient);
    221   GetWindowRect(handle(), &rcWindow);
    222 
    223   // Find offset between window size and client size
    224   POINT ptDiff;
    225   ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
    226   ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
    227 
    228   // Resize client
    229   MoveWindow(handle(), rcWindow.left, rcWindow.top,
    230              width + ptDiff.x, height + ptDiff.y, false);
    231   UpdateWindow(handle());
    232   ShowWindow(handle(), SW_SHOW);
    233 
    234   if (frame_changed && (width != bmi_.bmiHeader.biWidth ||
    235     height != -bmi_.bmiHeader.biHeight)) {
    236     // Update the bmi and image buffer
    237     bmi_.bmiHeader.biWidth = width;
    238     bmi_.bmiHeader.biHeight = -height;
    239     bmi_.bmiHeader.biSizeImage = width * height * 4;
    240     image_.reset(new uint8_t[bmi_.bmiHeader.biSizeImage]);
    241   }
    242 }
    243 
    244 void GdiVideoRenderer::VideoWindow::OnRenderFrame(const VideoFrame* frame) {
    245   if (!frame) {
    246     return;
    247   }
    248   // Convert frame to ARGB format, which is accepted by GDI
    249   frame->ConvertToRgbBuffer(cricket::FOURCC_ARGB, image_.get(),
    250                             bmi_.bmiHeader.biSizeImage,
    251                             bmi_.bmiHeader.biWidth * 4);
    252   InvalidateRect(handle(), 0, 0);
    253 }
    254 
    255 /////////////////////////////////////////////////////////////////////////////
    256 // Implementation of class GdiVideoRenderer
    257 /////////////////////////////////////////////////////////////////////////////
    258 GdiVideoRenderer::GdiVideoRenderer(int x, int y)
    259     : initial_x_(x),
    260       initial_y_(y) {
    261 }
    262 GdiVideoRenderer::~GdiVideoRenderer() {}
    263 
    264 bool GdiVideoRenderer::SetSize(int width, int height, int reserved) {
    265   if (!window_.get()) {  // Create the window for the first frame
    266     window_.reset(new VideoWindow(initial_x_, initial_y_, width, height));
    267   }
    268   return window_->SetSize(width, height);
    269 }
    270 
    271 bool GdiVideoRenderer::RenderFrame(const VideoFrame* frame) {
    272   if (!frame || !window_.get()) {
    273     return false;
    274   }
    275   return window_->RenderFrame(frame);
    276 }
    277 
    278 }  // namespace cricket
    279 #endif  // WIN32
    280