doubango/tinyDAV/src/video/gdi/tdav_consumer_video_gdi.c
c732d49e
 #if HAVE_CRT
 #define _CRTDBG_MAP_ALLOC 
 #include <stdlib.h> 
 #include <crtdbg.h>
 #endif //HAVE_CRT
 /*
74ca6d11
 * Copyright (C) 2020, University of the Basque Country (UPV/EHU)
c732d49e
 * Contact for licensing options: <licensing-mcpttclient(at)mcopenplatform(dot)com>
 *
 * The original file was part of Open Source Doubango Framework
 * Copyright (C) 2010-2011 Mamadou Diop.
 * Copyright (C) 2012 Doubango Telecom <http://doubango.org>
 *
 * 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 <windows.h>
 
 #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 */