#if HAVE_CRT #define _CRTDBG_MAP_ALLOC #include #include #endif //HAVE_CRT /* * Copyright (C) 2020, University of the Basque Country (UPV/EHU) * Contact for licensing options: * * The original file was part of Open Source Doubango Framework * Copyright (C) 2010-2011 Mamadou Diop. * Copyright (C) 2012 Doubango Telecom * * This file is part of Open Source Doubango Framework. * * DOUBANGO is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * DOUBANGO is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with DOUBANGO. * */ #include "tinydav/video/gdi/tdav_consumer_video_gdi.h" #if TDAV_UNDER_WINDOWS && !TDAV_UNDER_WINDOWS_RT #include #include "tsk_memory.h" #include "tsk_string.h" #include "tsk_safeobj.h" #include "tsk_debug.h" #define CHECK_HR(x) { HRESULT __hr__ = (x); if (FAILED(__hr__)) { TSK_DEBUG_ERROR("Operation Failed (%08x)", __hr__); goto bail; } } static HRESULT HookWindow(struct tdav_consumer_video_gdi_s *p_gdi, HWND hWnd, BOOL bFullScreenWindow); static HRESULT UnhookWindow(struct tdav_consumer_video_gdi_s *p_gdi, BOOL bFullScreenWindow); static HRESULT SetFullscreen(struct tdav_consumer_video_gdi_s *p_gdi, BOOL bFullScreen); static HWND CreateFullScreenWindow(struct tdav_consumer_video_gdi_s *p_gdi); static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); typedef struct tdav_consumer_video_gdi_s { TMEDIA_DECLARE_CONSUMER; BOOL bStarted, bPrepared, bPaused, bFullScreen, bWindowHooked, bWindowHookedFullScreen; HWND hWindow; WNDPROC wndProc; HWND hWindowFullScreen; WNDPROC wndProcFullScreen; BITMAPINFO bitmapInfo; void* pBuffer; TSK_DECLARE_SAFEOBJ; } tdav_consumer_video_gdi_t; /* ============ Media Consumer Interface ================= */ static int tdav_consumer_video_gdi_set(tmedia_consumer_t *self, const tmedia_param_t* param) { int ret = 0; tdav_consumer_video_gdi_t* p_gdi = (tdav_consumer_video_gdi_t*)self; HRESULT hr = S_OK; if (!self || !param) { TSK_DEBUG_ERROR("Invalid parameter"); CHECK_HR(hr = E_POINTER); } if (param->value_type == tmedia_pvt_int64) { if (tsk_striequals(param->key, "remote-hwnd")) { HWND hWnd = ((HWND)*((int64_t*)param->value)); if (hWnd != p_gdi->hWindow) { tsk_safeobj_lock(p_gdi); // block consumer thread UnhookWindow(p_gdi, FALSE); p_gdi->hWindow = hWnd; tsk_safeobj_unlock(p_gdi); // unblock consumer thread } } } else if(param->value_type == tmedia_pvt_int32) { if(tsk_striequals(param->key, "fullscreen")) { BOOL bFullScreen = !!*((int32_t*)param->value); TSK_DEBUG_INFO("[GDI video consumer] Full Screen = %d", bFullScreen); CHECK_HR(hr = SetFullscreen(p_gdi, bFullScreen)); } } CHECK_HR(hr); bail: return SUCCEEDED(hr) ? 0 : -1; } static int tdav_consumer_video_gdi_prepare(tmedia_consumer_t* self, const tmedia_codec_t* codec) { tdav_consumer_video_gdi_t* p_gdi = (tdav_consumer_video_gdi_t*)self; if (!p_gdi || !codec && codec->plugin) { TSK_DEBUG_ERROR("Invalid parameter"); return -1; } TMEDIA_CONSUMER(p_gdi)->video.fps = TMEDIA_CODEC_VIDEO(codec)->in.fps; TMEDIA_CONSUMER(p_gdi)->video.in.width = TMEDIA_CODEC_VIDEO(codec)->in.width; TMEDIA_CONSUMER(p_gdi)->video.in.height = TMEDIA_CODEC_VIDEO(codec)->in.height; if (!TMEDIA_CONSUMER(p_gdi)->video.display.width) { TMEDIA_CONSUMER(p_gdi)->video.display.width = TMEDIA_CONSUMER(p_gdi)->video.in.width; } if (!TMEDIA_CONSUMER(p_gdi)->video.display.height) { TMEDIA_CONSUMER(p_gdi)->video.display.height = TMEDIA_CONSUMER(p_gdi)->video.in.height; } ZeroMemory(&p_gdi->bitmapInfo, sizeof(p_gdi->bitmapInfo)); p_gdi->bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); p_gdi->bitmapInfo.bmiHeader.biPlanes = 1; p_gdi->bitmapInfo.bmiHeader.biBitCount = 24; // RGB24 p_gdi->bitmapInfo.bmiHeader.biCompression = BI_RGB; p_gdi->bitmapInfo.bmiHeader.biWidth = (LONG)TMEDIA_CONSUMER(p_gdi)->video.in.width; p_gdi->bitmapInfo.bmiHeader.biHeight = (LONG)(TMEDIA_CONSUMER(p_gdi)->video.in.height * -1); p_gdi->bitmapInfo.bmiHeader.biSizeImage = (DWORD)(TMEDIA_CONSUMER(p_gdi)->video.in.width * abs((int)TMEDIA_CONSUMER(p_gdi)->video.in.height) * (p_gdi->bitmapInfo.bmiHeader.biBitCount >> 3)); return 0; } static int tdav_consumer_video_gdi_start(tmedia_consumer_t* self) { tdav_consumer_video_gdi_t* p_gdi = (tdav_consumer_video_gdi_t*)self; if (!p_gdi) { TSK_DEBUG_ERROR("Invalid parameter"); return -1; } tsk_safeobj_lock(p_gdi); p_gdi->bPaused = FALSE; p_gdi->bStarted = TRUE; tsk_safeobj_unlock(p_gdi); return 0; } static int tdav_consumer_video_gdi_consume(tmedia_consumer_t* self, const void* buffer, tsk_size_t size, const tsk_object_t* proto_hdr) { tdav_consumer_video_gdi_t* p_gdi = (tdav_consumer_video_gdi_t*)self; int ret = 0; HWND* p_Window; BOOL* p_bWindowHooked; if (!p_gdi) { TSK_DEBUG_ERROR("Invalid parameter"); return -1; } tsk_safeobj_lock(p_gdi); if (!p_gdi->bStarted || p_gdi->bPaused) { TSK_DEBUG_INFO("GDI consumer stopped or paused"); goto bail; } if (p_gdi->bitmapInfo.bmiHeader.biSizeImage != size) { tsk_size_t xNewSize = TMEDIA_CONSUMER(p_gdi)->video.in.width * TMEDIA_CONSUMER(p_gdi)->video.in.height * (p_gdi->bitmapInfo.bmiHeader.biBitCount >> 3); TSK_DEBUG_INFO("GDI input size changed: %u->%u", p_gdi->bitmapInfo.bmiHeader.biSizeImage, size); if (xNewSize != size) { TSK_DEBUG_ERROR("GDI consumer: chroma issue?"); ret = -1; goto bail; } p_gdi->bitmapInfo.bmiHeader.biWidth = (LONG)TMEDIA_CONSUMER(p_gdi)->video.in.width; p_gdi->bitmapInfo.bmiHeader.biHeight = (LONG)TMEDIA_CONSUMER(p_gdi)->video.in.height * -1; p_gdi->bitmapInfo.bmiHeader.biSizeImage = (DWORD)xNewSize; p_gdi->pBuffer = tsk_realloc(p_gdi->pBuffer, p_gdi->bitmapInfo.bmiHeader.biSizeImage); } p_Window = p_gdi->bFullScreen ? &p_gdi->hWindowFullScreen : &p_gdi->hWindow; p_bWindowHooked = p_gdi->bFullScreen ? &p_gdi->bWindowHookedFullScreen : &p_gdi->bWindowHooked; if (*p_Window) { if (!*p_bWindowHooked) { // Do not hook "hWnd" as it could be the fullscreen handle which is always hooked. CHECK_HR(HookWindow(p_gdi, *p_Window, p_gdi->bFullScreen)); } if (!p_gdi->pBuffer) { p_gdi->pBuffer = tsk_realloc(p_gdi->pBuffer, p_gdi->bitmapInfo.bmiHeader.biSizeImage); } if (p_gdi->pBuffer) { memcpy(p_gdi->pBuffer, buffer, size); InvalidateRect(*p_Window, NULL, TRUE); } } bail: tsk_safeobj_unlock(p_gdi); return ret; } static int tdav_consumer_video_gdi_pause(tmedia_consumer_t* self) { tdav_consumer_video_gdi_t* p_gdi = (tdav_consumer_video_gdi_t*)self; if (!p_gdi) { TSK_DEBUG_ERROR("Invalid parameter"); return -1; } tsk_safeobj_lock(p_gdi); p_gdi->bPaused = TRUE; tsk_safeobj_unlock(p_gdi); return 0; } static int tdav_consumer_video_gdi_stop(tmedia_consumer_t* self) { tdav_consumer_video_gdi_t* p_gdi = (tdav_consumer_video_gdi_t*)self; if (!p_gdi) { TSK_DEBUG_ERROR("Invalid parameter"); return -1; } tsk_safeobj_lock(p_gdi); p_gdi->bStarted = FALSE; p_gdi->bPaused = FALSE; SetFullscreen(p_gdi, FALSE); UnhookWindow(p_gdi, TRUE); UnhookWindow(p_gdi, FALSE); tsk_safeobj_unlock(p_gdi); return 0; } static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_CREATE: case WM_SIZE: case WM_MOVE: { struct tdav_consumer_video_gdi_s* p_gdi = ((struct tdav_consumer_video_gdi_s*)GetProp(hWnd, TEXT("Self"))); if (p_gdi) { } break; } case WM_PAINT: { struct tdav_consumer_video_gdi_s* p_gdi = ((struct tdav_consumer_video_gdi_s*)GetProp(hWnd, TEXT("Self"))); if (p_gdi) { tsk_safeobj_lock(p_gdi); if (p_gdi->bStarted && !p_gdi->bPaused && p_gdi->pBuffer) { PAINTSTRUCT ps; HDC hdc; RECT rc, logical_rect; int height, width, i, x, y; HDC dc_mem, all_dc[2]; HBITMAP bmp_mem; HGDIOBJ bmp_old; POINT logical_area; HBRUSH brush; if (!(hdc = BeginPaint(hWnd, &ps))) { goto paint_done; } if (!GetClientRect(hWnd, &rc)) { EndPaint(hWnd, &ps); goto paint_done; } height = abs(p_gdi->bitmapInfo.bmiHeader.biHeight); width = p_gdi->bitmapInfo.bmiHeader.biWidth; dc_mem = CreateCompatibleDC(ps.hdc); SetStretchBltMode(dc_mem, HALFTONE); // Set the map mode so that the ratio will be maintained for us. all_dc[0] = ps.hdc, all_dc[1] = dc_mem; for (i = 0; i < sizeof(all_dc)/sizeof(all_dc[0]); ++i) { #if !TDAV_UNDER_WINDOWS_CE SetMapMode(all_dc[i], MM_ISOTROPIC); SetWindowExtEx(all_dc[i], width, height, NULL); SetViewportExtEx(all_dc[i], rc.right, rc.bottom, NULL); #endif } bmp_mem = CreateCompatibleBitmap(ps.hdc, rc.right, rc.bottom); bmp_old = SelectObject(dc_mem, bmp_mem); logical_area.x = rc.right, logical_area.y = rc.bottom; #if !TDAV_UNDER_WINDOWS_CE DPtoLP(ps.hdc, &logical_area, 1); #endif brush = CreateSolidBrush(RGB(0, 0, 0)); logical_rect.left = 0, logical_rect.top = 0, logical_rect.right = logical_area.x, logical_rect.bottom = logical_area.y; FillRect(dc_mem, &logical_rect, brush); DeleteObject(brush); x = (logical_area.x / 2) - (width / 2); y = (logical_area.y / 2) - (height / 2); StretchDIBits(dc_mem, x, y, width, height, 0, 0, width, height, p_gdi->pBuffer, &p_gdi->bitmapInfo, 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); EndPaint(hWnd, &ps); } paint_done: tsk_safeobj_unlock(p_gdi); } break; } case WM_ERASEBKGND: { return TRUE; // avoid background erasing. } case WM_CHAR: case WM_KEYUP: { struct tdav_consumer_video_gdi_s* p_gdi = ((struct tdav_consumer_video_gdi_s*)GetProp(hWnd, TEXT("Self"))); if (p_gdi) { SetFullscreen(p_gdi, FALSE); } break; } } return DefWindowProc(hWnd, uMsg, wParam, lParam); } static HRESULT HookWindow(struct tdav_consumer_video_gdi_s *p_gdi, HWND hWnd, BOOL bFullScreenWindow) { HRESULT hr = S_OK; HWND* p_Window = bFullScreenWindow ? &p_gdi->hWindowFullScreen : &p_gdi->hWindow; WNDPROC* p_wndProc = bFullScreenWindow ? &p_gdi->wndProcFullScreen : &p_gdi->wndProc; BOOL* p_bWindowHooked = bFullScreenWindow ? &p_gdi->bWindowHookedFullScreen : &p_gdi->bWindowHooked; tsk_safeobj_lock(p_gdi); CHECK_HR(hr = UnhookWindow(p_gdi, bFullScreenWindow)); if ((*p_Window = hWnd)) { #if TDAV_UNDER_WINDOWS_CE *p_wndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (LONG)WndProc); #else *p_wndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)WndProc); #endif if (!*p_wndProc) { TSK_DEBUG_ERROR("HookWindowLongPtr() failed with errcode=%d", GetLastError()); CHECK_HR(hr = E_FAIL); } *p_bWindowHooked = TRUE; SetProp(*p_Window, TEXT("Self"), p_gdi); } bail: tsk_safeobj_unlock(p_gdi); return S_OK; } static HRESULT UnhookWindow(struct tdav_consumer_video_gdi_s *p_gdi, BOOL bFullScreenWindow) { HWND* p_Window = bFullScreenWindow ? &p_gdi->hWindowFullScreen : &p_gdi->hWindow; WNDPROC* p_wndProc = bFullScreenWindow ? &p_gdi->wndProcFullScreen : &p_gdi->wndProc; BOOL* p_bWindowHooked = bFullScreenWindow ? &p_gdi->bWindowHookedFullScreen : &p_gdi->bWindowHooked; tsk_safeobj_lock(p_gdi); if (*p_Window && *p_wndProc) { #if TDAV_UNDER_WINDOWS_CE SetWindowLong(*p_Window, GWL_WNDPROC, (LONG)*p_wndProc); #else SetWindowLongPtr(*p_Window, GWLP_WNDPROC, (LONG_PTR)*p_wndProc); #endif *p_wndProc = NULL; } if (*p_Window) { if (p_gdi->pBuffer) { memset(p_gdi->pBuffer, 0, p_gdi->bitmapInfo.bmiHeader.biSizeImage); } InvalidateRect(*p_Window, NULL, FALSE); } *p_bWindowHooked = FALSE; tsk_safeobj_unlock(p_gdi); return S_OK; } static HRESULT SetFullscreen(struct tdav_consumer_video_gdi_s *p_gdi, BOOL bFullScreen) { HRESULT hr = S_OK; if (!p_gdi) { CHECK_HR(hr = E_POINTER); } if (p_gdi->bFullScreen != bFullScreen) { tsk_safeobj_lock(p_gdi); if (bFullScreen) { HWND hWnd = CreateFullScreenWindow(p_gdi); if (hWnd) { #if TDAV_UNDER_WINDOWS_CE ShowWindow(hWnd, SW_SHOWNORMAL); #else ShowWindow(hWnd, SW_SHOWDEFAULT); #endif UpdateWindow(hWnd); HookWindow(p_gdi, hWnd, TRUE); } } else if(p_gdi->hWindowFullScreen) { ShowWindow(p_gdi->hWindowFullScreen, SW_HIDE); UnhookWindow(p_gdi, TRUE); } p_gdi->bFullScreen = bFullScreen; tsk_safeobj_unlock(p_gdi); CHECK_HR(hr); } bail: return hr; } static HWND CreateFullScreenWindow(struct tdav_consumer_video_gdi_s *p_gdi) { HRESULT hr = S_OK; if(!p_gdi) { return NULL; } if (!p_gdi->hWindowFullScreen) { WNDCLASS wc = {0}; wc.lpfnWndProc = WndProc; wc.hInstance = GetModuleHandle(NULL); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.lpszClassName = L"WindowClass"; RegisterClass(&wc); p_gdi->hWindowFullScreen = CreateWindowEx( 0, wc.lpszClassName, L"Doubango's Video Consumer Fullscreen", WS_EX_TOPMOST | WS_POPUP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), NULL, NULL, GetModuleHandle(NULL), NULL); SetProp(p_gdi->hWindowFullScreen, TEXT("Self"), p_gdi); } return p_gdi->hWindowFullScreen; } // // GDI video consumer object definition // /* constructor */ static tsk_object_t* tdav_consumer_video_gdi_ctor(tsk_object_t * self, va_list * app) { tdav_consumer_video_gdi_t *p_gdi = (tdav_consumer_video_gdi_t *)self; if (p_gdi) { /* init base */ tmedia_consumer_init(TMEDIA_CONSUMER(p_gdi)); TMEDIA_CONSUMER(p_gdi)->video.display.chroma = tmedia_chroma_bgr24; /* init self */ TMEDIA_CONSUMER(p_gdi)->video.fps = 15; TMEDIA_CONSUMER(p_gdi)->video.display.width = 352; TMEDIA_CONSUMER(p_gdi)->video.display.height = 288; TMEDIA_CONSUMER(p_gdi)->video.display.auto_resize = tsk_true; tsk_safeobj_init(p_gdi); } return self; } /* destructor */ static tsk_object_t* tdav_consumer_video_gdi_dtor(tsk_object_t * self) { tdav_consumer_video_gdi_t *p_gdi = (tdav_consumer_video_gdi_t *)self; if (p_gdi) { /* stop */ tdav_consumer_video_gdi_stop((tmedia_consumer_t*)self); /* deinit base */ tmedia_consumer_deinit(TMEDIA_CONSUMER(p_gdi)); /* deinit self */ TSK_FREE(p_gdi->pBuffer); tsk_safeobj_deinit(p_gdi); } return self; } /* object definition */ static const tsk_object_def_t tdav_consumer_video_gdi_def_s = { sizeof(tdav_consumer_video_gdi_t), tdav_consumer_video_gdi_ctor, tdav_consumer_video_gdi_dtor, tsk_null, }; /* plugin definition*/ static const tmedia_consumer_plugin_def_t tdav_consumer_video_gdi_plugin_def_s = { &tdav_consumer_video_gdi_def_s, tmedia_video, "Microsoft GDI consumer (using custom source)", tdav_consumer_video_gdi_set, tdav_consumer_video_gdi_prepare, tdav_consumer_video_gdi_start, tdav_consumer_video_gdi_consume, tdav_consumer_video_gdi_pause, tdav_consumer_video_gdi_stop }; const tmedia_consumer_plugin_def_t *tdav_consumer_video_gdi_plugin_def_t = &tdav_consumer_video_gdi_plugin_def_s; #endif /* TDAV_UNDER_WINDOWS && !TDAV_UNDER_WINDOWS_RT */