diff --git a/peerconnection/samples/client/conductor.cc b/peerconnection/samples/client/conductor.cc index e0c029fb57..a4c104a034 100644 --- a/peerconnection/samples/client/conductor.cc +++ b/peerconnection/samples/client/conductor.cc @@ -71,8 +71,6 @@ bool Conductor::InitializePeerConnection() { void Conductor::DeletePeerConnection() { peer_connection_.reset(); - local_renderer_.reset(); - remote_renderer_.reset(); handshake_ = NONE; } @@ -82,14 +80,7 @@ void Conductor::StartCaptureDevice() { main_wnd_->SwitchToStreamingUI(); if (peer_connection_->SetVideoCapture("")) { - if (!local_renderer_.get()) { - // The window will be resized according to the stream properties - // when streaming starts. - local_renderer_.reset( - cricket::VideoRendererFactory::CreateGuiVideoRenderer(100, 100)); - } - if (local_renderer_.get()) - peer_connection_->SetLocalVideoRenderer(local_renderer_.get()); + peer_connection_->SetLocalVideoRenderer(main_wnd_->local_renderer()); } else { ASSERT(false); } @@ -139,13 +130,8 @@ void Conductor::OnAddStream(const std::string& stream_id, int channel_id, video_channel_ = channel_id; waiting_for_video_ = false; LOG(INFO) << "Setting video renderer for channel: " << channel_id; - if (!remote_renderer_.get()) { - // The window size will be automatically corrected. - remote_renderer_.reset( - cricket::VideoRendererFactory::CreateGuiVideoRenderer(100, 100)); - } bool ok = peer_connection_->SetVideoRenderer(stream_id, - remote_renderer_.get()); + main_wnd_->remote_renderer()); ASSERT(ok); } else { ASSERT(audio_channel_ == -1); diff --git a/peerconnection/samples/client/conductor.h b/peerconnection/samples/client/conductor.h index 3b9d471b48..3fd3daa6f9 100644 --- a/peerconnection/samples/client/conductor.h +++ b/peerconnection/samples/client/conductor.h @@ -115,8 +115,6 @@ class Conductor MainWnd* main_wnd_; int video_channel_; int audio_channel_; - talk_base::scoped_ptr local_renderer_; - talk_base::scoped_ptr remote_renderer_; }; #endif // PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_ diff --git a/peerconnection/samples/client/main_wnd.cc b/peerconnection/samples/client/main_wnd.cc index e75133f4f3..b6b125fd31 100644 --- a/peerconnection/samples/client/main_wnd.cc +++ b/peerconnection/samples/client/main_wnd.cc @@ -10,6 +10,8 @@ #include "peerconnection/samples/client/main_wnd.h" +#include + #include "talk/base/common.h" #include "talk/base/logging.h" @@ -145,6 +147,7 @@ void MainWnd::SwitchToPeerList(const Peers& peers) { ui_ = LIST_PEERS; LayoutPeerListUI(true); + ::SetFocus(listbox_); } void MainWnd::SwitchToStreamingUI() { @@ -159,9 +162,64 @@ void MainWnd::OnPaint() { RECT rc; ::GetClientRect(handle(), &rc); - HBRUSH brush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW)); - ::FillRect(ps.hdc, &rc, brush); - ::DeleteObject(brush); + + if (ui_ == STREAMING && remote_video_.get() && local_video_.get()) { + const BITMAPINFO& bmi = remote_video_->bmi(); + long height = abs(bmi.bmiHeader.biHeight); + long width = bmi.bmiHeader.biWidth; + + HDC dc_mem = ::CreateCompatibleDC(ps.hdc); + + // Set the map mode so that the ratio will be maintained for us. + HDC all_dc[] = { ps.hdc, dc_mem }; + for (int i = 0; i < ARRAY_SIZE(all_dc); ++i) { + SetMapMode(all_dc[i], MM_ISOTROPIC); + SetWindowExtEx(all_dc[i], width, height, NULL); + SetViewportExtEx(all_dc[i], rc.right, rc.bottom, NULL); + } + + HBITMAP bmp_mem = ::CreateCompatibleBitmap(ps.hdc, rc.right, rc.bottom); + HGDIOBJ bmp_old = ::SelectObject(dc_mem, bmp_mem); + HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0)); + ::FillRect(dc_mem, &rc, brush); + ::DeleteObject(brush); + + POINT logical_area = { rc.right, rc.bottom }; + DPtoLP(ps.hdc, &logical_area, 1); + + const uint8* image = remote_video_->image(); + int max_unit = std::max(width, height); + int x = (logical_area.x / 2) - (width / 2); + int y = (logical_area.y / 2) - (height / 2); + + StretchDIBits(dc_mem, x, y, width, height, + 0, 0, width, height, image, &bmi, DIB_RGB_COLORS, SRCCOPY); + + if ((rc.right - rc.left) > 200 && (rc.bottom - rc.top) > 200) { + const BITMAPINFO& bmi = local_video_->bmi(); + image = local_video_->image(); + long thumb_width = bmi.bmiHeader.biWidth / 4; + long thumb_height = abs(bmi.bmiHeader.biHeight) / 4; + StretchDIBits(dc_mem, + logical_area.x - thumb_width - 10, + logical_area.y - thumb_height - 10, + thumb_width, thumb_height, + 0, 0, bmi.bmiHeader.biWidth, -bmi.bmiHeader.biHeight, + image, &bmi, DIB_RGB_COLORS, SRCCOPY); + } + + BitBlt(ps.hdc, 0, 0, logical_area.x, logical_area.y, + dc_mem, 0, 0, SRCCOPY); + + // Cleanup. + ::SelectObject(dc_mem, bmp_old); + ::DeleteObject(bmp_mem); + ::DeleteDC(dc_mem); + } else { + HBRUSH brush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW)); + ::FillRect(ps.hdc, &rc, brush); + ::DeleteObject(brush); + } ::EndPaint(handle(), &ps); } @@ -193,6 +251,11 @@ void MainWnd::OnDefaultAction() { bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result) { switch (msg) { + case WM_CREATE: + remote_video_.reset(new VideoRenderer(handle(), 1, 1)); + local_video_.reset(new VideoRenderer(handle(), 1, 1)); + break; + case WM_ERASEBKGND: *result = TRUE; return true; @@ -202,6 +265,8 @@ bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result) { case WM_SETFOCUS: if (ui_ == CONNECT_TO_SERVER) { SetFocus(edit1_); + } else if (ui_ == LIST_PEERS) { + SetFocus(listbox_); } return true; case WM_SIZE: @@ -224,6 +289,12 @@ bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result) { } } return true; + case VIDEO_RENDERER_MESSAGE: { + VideoRenderer* renderer = reinterpret_cast(lp); + const MSG* msg_ptr = reinterpret_cast(wp); + renderer->OnMessage(*msg_ptr); + return true; + } } return false; } @@ -394,3 +465,78 @@ void MainWnd::HandleTabbing() { } while (true); ::SetFocus(next); } + +// +// MainWnd::VideoRenderer +// + +MainWnd::VideoRenderer::VideoRenderer(HWND wnd, int width, int height) + : wnd_(wnd) { + ZeroMemory(&bmi_, sizeof(bmi_)); + bmi_.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi_.bmiHeader.biPlanes = 1; + bmi_.bmiHeader.biBitCount = 32; + bmi_.bmiHeader.biCompression = BI_RGB; + bmi_.bmiHeader.biWidth = width; + bmi_.bmiHeader.biHeight = -height; + bmi_.bmiHeader.biSizeImage = width * height * + (bmi_.bmiHeader.biBitCount >> 3); + image_.reset(new uint8[bmi_.bmiHeader.biSizeImage]); +} + +MainWnd::VideoRenderer::~VideoRenderer() { +} + +bool MainWnd::VideoRenderer::SetSize(int width, int height, int reserved) { + if (width != bmi_.bmiHeader.biWidth || + height != -bmi_.bmiHeader.biHeight) { + // Update the bitmap info and image buffer. + // To avoid touching buffers from different threads, we always + // marshal messages through the main window's thread. + MSG msg = {0}; + msg.message = WM_SIZE; + msg.lParam = width; + msg.wParam = height; + ::SendMessage(wnd_, VIDEO_RENDERER_MESSAGE, + reinterpret_cast(&msg), + reinterpret_cast(this)); + } + return true; +} + +bool MainWnd::VideoRenderer::RenderFrame(const cricket::VideoFrame* frame) { + if (!frame) + return false; + + MSG msg = {0}; + msg.message = WM_PAINT; + msg.lParam = reinterpret_cast(frame); + ::SendMessage(wnd_, VIDEO_RENDERER_MESSAGE, + reinterpret_cast(&msg), + reinterpret_cast(this)); + return true; +} + +void MainWnd::VideoRenderer::OnMessage(const MSG& msg) { + switch (msg.message) { + case WM_SIZE: + bmi_.bmiHeader.biWidth = static_cast(msg.lParam); + bmi_.bmiHeader.biHeight = -static_cast(msg.wParam); + bmi_.bmiHeader.biSizeImage = bmi_.bmiHeader.biWidth * + static_cast(msg.wParam) * + (bmi_.bmiHeader.biBitCount >> 3); + image_.reset(new uint8[bmi_.bmiHeader.biSizeImage]); + break; + + case WM_PAINT: { + const cricket::VideoFrame* frame = + reinterpret_cast(msg.lParam); + frame->ConvertToRgbBuffer(cricket::FOURCC_ARGB, image_.get(), + bmi_.bmiHeader.biSizeImage, + bmi_.bmiHeader.biWidth * + (bmi_.bmiHeader.biBitCount >> 3)); + InvalidateRect(wnd_, 0, 0); + break; + } + } +} diff --git a/peerconnection/samples/client/main_wnd.h b/peerconnection/samples/client/main_wnd.h index 0fadfd8fa2..d72792cf53 100644 --- a/peerconnection/samples/client/main_wnd.h +++ b/peerconnection/samples/client/main_wnd.h @@ -17,6 +17,8 @@ #include "peerconnection/samples/client/peer_connection_client.h" #include "talk/base/win32.h" +#include "talk/session/phone/mediachannel.h" +#include "talk/session/phone/videocommon.h" class MainWndCallback { public: @@ -38,6 +40,10 @@ class MainWnd { STREAMING, }; + enum WindowMessages { + VIDEO_RENDERER_MESSAGE = WM_APP + 1, + }; + MainWnd(); ~MainWnd(); @@ -56,6 +62,40 @@ class MainWnd { HWND handle() const { return wnd_; } UI current_ui() const { return ui_; } + cricket::VideoRenderer* local_renderer() const { + return local_video_.get(); + } + + cricket::VideoRenderer* remote_renderer() const { + return remote_video_.get(); + } + + class VideoRenderer : public cricket::VideoRenderer { + public: + VideoRenderer(HWND wnd, int width, int height); + virtual ~VideoRenderer(); + + virtual bool SetSize(int width, int height, int reserved); + + // Called when a new frame is available for display. + virtual bool RenderFrame(const cricket::VideoFrame* frame); + + void OnMessage(const MSG& msg); + + const BITMAPINFO& bmi() const { return bmi_; } + const uint8* image() const { return image_.get(); } + + protected: + enum { + SET_SIZE, + RENDER_FRAME, + }; + + HWND wnd_; + BITMAPINFO bmi_; + talk_base::scoped_array image_; + }; + protected: enum ChildWindowID { EDIT_ID = 1, @@ -85,6 +125,8 @@ class MainWnd { void HandleTabbing(); private: + talk_base::scoped_ptr remote_video_; + talk_base::scoped_ptr local_video_; UI ui_; HWND wnd_; HWND edit1_;