doubango/tinyDAV/src/video/gdi/tdav_producer_screencast_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_producer_screencast_gdi.h"
 
 #if TDAV_UNDER_WINDOWS && !TDAV_UNDER_WINDOWS_RT
 
 #include <windows.h>
 
 #define RESIZER_DO_NOT_INCLUDE_HEADER
 #include "..\..\..\..\plugins\pluginDirectShow\internals\Resizer.cxx"
 
 #include "tsk_memory.h"
 #include "tsk_safeobj.h"
 #include "tsk_timer.h"
 #include "tsk_string.h"
 #include "tsk_debug.h"
 
 #if TDAV_UNDER_WINDOWS_CE
 static const BOOL bitmapBuffSrcOwnMemory = FALSE;
 #else
 static const BOOL bitmapBuffSrcOwnMemory = TRUE;
 #endif /* TDAV_UNDER_WINDOWS_CE */
 
 #if !defined(kMaxFrameRate)
 #	define kMaxFrameRate 4 // FIXME
 #endif /* kMaxFrameRate */
 
 typedef struct tdav_producer_screencast_gdi_s
 {
 	TMEDIA_DECLARE_PRODUCER;
 	
 	HWND hwnd_preview;
 	HWND hwnd_src;
 
 	tsk_timer_manager_handle_t *p_timer_mgr;
 	tsk_timer_id_t id_timer_grab;
 	uint64_t u_timout_grab;
 
 	BITMAPINFO bitmapInfoSrc;
 	BITMAPINFO bitmapInfoNeg;
 
 	void* p_buff_src; // must use VirtualAlloc()
 	tsk_size_t n_buff_src;
 	void* p_buff_neg; // must use VirtualAlloc()
 	tsk_size_t n_buff_neg;
 	
 	tsk_bool_t b_started;
 	tsk_bool_t b_paused;
 	tsk_bool_t b_muted;
 
 	RECT rcScreen;
 
 	TSK_DECLARE_SAFEOBJ;
 }
 tdav_producer_screencast_gdi_t;
 
 static int _tdav_producer_screencast_timer_cb(const void* arg, tsk_timer_id_t timer_id);
 static int _tdav_producer_screencast_grab(tdav_producer_screencast_gdi_t* p_self);
 
 
 /* ============ Media Producer Interface ================= */
 static int _tdav_producer_screencast_gdi_set(tmedia_producer_t *p_self, const tmedia_param_t* pc_param)
 {
 	int ret = 0;
 	tdav_producer_screencast_gdi_t* p_gdi = (tdav_producer_screencast_gdi_t*)p_self;
 
 	if (!p_gdi || !pc_param) {
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	if (pc_param->value_type == tmedia_pvt_int64) {
 		if (tsk_striequals(pc_param->key, "local-hwnd") || tsk_striequals(pc_param->key, "preview-hwnd")) {
 			p_gdi->hwnd_preview = (HWND)*((int64_t*)pc_param->value);
 		}
 		else if (tsk_striequals(pc_param->key, "src-hwnd")) {
 			p_gdi->hwnd_src = (HWND)*((int64_t*)pc_param->value);
 		}
 	}
 	else if (pc_param->value_type == tmedia_pvt_int32) {
 		if (tsk_striequals(pc_param->key, "mute")) {
 			p_gdi->b_muted = (TSK_TO_INT32((uint8_t*)pc_param->value) != 0);
 		}
 	}
 	
 	return ret;
 }
 
 static int _tdav_producer_screencast_gdi_prepare(tmedia_producer_t* p_self, const tmedia_codec_t* pc_codec)
 {
 	tdav_producer_screencast_gdi_t* p_gdi = (tdav_producer_screencast_gdi_t*)p_self;
 	int ret = 0;
 
 	if (!p_gdi || !pc_codec) {
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	tsk_safeobj_lock(p_gdi);
 
 	if (!p_gdi->p_timer_mgr && !(p_gdi->p_timer_mgr = tsk_timer_manager_create())) {
 		TSK_DEBUG_ERROR("Failed to create timer manager");
 		ret = -2;
 		goto bail;
 	}
 	
 #if METROPOLIS /*= G2J.COM */
 	TMEDIA_PRODUCER(p_gdi)->video.fps = TSK_MIN(TMEDIA_CODEC_VIDEO(pc_codec)->out.fps, kMaxFrameRate);
 #else
 	TMEDIA_PRODUCER(p_gdi)->video.fps = TMEDIA_CODEC_VIDEO(pc_codec)->out.fps;
 #endif
 	TMEDIA_PRODUCER(p_gdi)->video.width = TMEDIA_CODEC_VIDEO(pc_codec)->out.width;
 	TMEDIA_PRODUCER(p_gdi)->video.height = TMEDIA_CODEC_VIDEO(pc_codec)->out.height;
 
 	p_gdi->bitmapInfoNeg.bmiHeader.biSize = p_gdi->bitmapInfoSrc.bmiHeader.biSize = (DWORD)sizeof(BITMAPINFOHEADER);
 	p_gdi->bitmapInfoNeg.bmiHeader.biWidth = p_gdi->bitmapInfoSrc.bmiHeader.biWidth = (LONG)TMEDIA_PRODUCER(p_gdi)->video.width;
 	p_gdi->bitmapInfoNeg.bmiHeader.biHeight = p_gdi->bitmapInfoSrc.bmiHeader.biHeight = (LONG)TMEDIA_PRODUCER(p_gdi)->video.height;
 	p_gdi->bitmapInfoNeg.bmiHeader.biPlanes = p_gdi->bitmapInfoSrc.bmiHeader.biPlanes = 1;
 	p_gdi->bitmapInfoNeg.bmiHeader.biBitCount = p_gdi->bitmapInfoSrc.bmiHeader.biBitCount = 24; 
 	p_gdi->bitmapInfoNeg.bmiHeader.biCompression = p_gdi->bitmapInfoSrc.bmiHeader.biCompression = BI_RGB;
 	p_gdi->bitmapInfoNeg.bmiHeader.biSizeImage = (p_gdi->bitmapInfoNeg.bmiHeader.biWidth * p_gdi->bitmapInfoNeg.bmiHeader.biHeight * (p_gdi->bitmapInfoNeg.bmiHeader.biBitCount >> 3));
 	
 	if (p_gdi->n_buff_neg < p_gdi->bitmapInfoNeg.bmiHeader.biSizeImage) {
 		if (p_gdi->p_buff_neg) VirtualFree(p_gdi->p_buff_neg, 0, MEM_RELEASE);
 		if (!(p_gdi->p_buff_neg = VirtualAlloc(NULL, p_gdi->bitmapInfoNeg.bmiHeader.biSizeImage, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE))) {
 			p_gdi->n_buff_neg = 0;
 			ret = -3;
 			goto bail;
 		}
 		p_gdi->n_buff_neg = p_gdi->bitmapInfoNeg.bmiHeader.biSizeImage;
 	}
 
 	/* Get screen size */ {
 		HDC hDC;
 		hDC = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL);
 		if (!hDC) {
 			TSK_DEBUG_ERROR("CreateDC failed");
 			ret = -4;
 			goto bail;
 		}
 
 		// Get the dimensions of the main desktop window
 		p_gdi->rcScreen.left = p_gdi->rcScreen.top = 0;
 		p_gdi->rcScreen.right = GetDeviceCaps(hDC, HORZRES);
 		p_gdi->rcScreen.bottom = GetDeviceCaps(hDC, VERTRES);
 
 		// Release the device context
 		DeleteDC(hDC);
 	}
 
 	p_gdi->u_timout_grab = (1000/TMEDIA_PRODUCER(p_gdi)->video.fps);
 	
 bail:
 	tsk_safeobj_unlock(p_gdi);
 	return ret;
 }
 
 static int _tdav_producer_screencast_gdi_start(tmedia_producer_t* p_self)
 {
 	tdav_producer_screencast_gdi_t* p_gdi = (tdav_producer_screencast_gdi_t*)p_self;
 	int ret = 0;
 		
 	if (!p_gdi) {
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	tsk_safeobj_lock(p_gdi);
 
 	p_gdi->b_paused = tsk_false;
 
 	if (p_gdi->b_started) {
 		TSK_DEBUG_INFO("GDI screencast producer already started");
 		goto bail;
 	}
 
 	if ((ret = tsk_timer_manager_start(p_gdi->p_timer_mgr))) {
 		goto bail;
 	}
 	
 	p_gdi->b_started = tsk_true;
 
 	p_gdi->id_timer_grab = tsk_timer_manager_schedule(p_gdi->p_timer_mgr, p_gdi->u_timout_grab, _tdav_producer_screencast_timer_cb, p_gdi);
 	if (!TSK_TIMER_ID_IS_VALID(p_gdi->id_timer_grab)) {
 		TSK_DEBUG_ERROR("Failed to schedule timer with timeout=%llu", p_gdi->u_timout_grab);
 		ret = -2;
 		goto bail;
 	}
 
 bail:
 	if (ret) {
 		p_gdi->b_started = tsk_false;
 		if (p_gdi->p_timer_mgr) {
 			tsk_timer_manager_start(p_gdi->p_timer_mgr);
 		}
 	}
 	tsk_safeobj_unlock(p_gdi);
 
 	return ret;
 }
 
 static int _tdav_producer_screencast_gdi_pause(tmedia_producer_t* p_self)
 {
 	tdav_producer_screencast_gdi_t* p_gdi = (tdav_producer_screencast_gdi_t*)p_self;
 
 	if (!p_gdi) {
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	tsk_safeobj_lock(p_gdi);
 
 	p_gdi->b_paused = tsk_true;
 	goto bail;
 
 bail:
 	tsk_safeobj_unlock(p_gdi);
 
 	return 0;
 }
 
 static int _tdav_producer_screencast_gdi_stop(tmedia_producer_t* p_self)
 {
 	tdav_producer_screencast_gdi_t* p_gdi = (tdav_producer_screencast_gdi_t*)p_self;
 
 	if (!p_gdi) {
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	tsk_safeobj_lock(p_gdi);
 	
 	if (!p_gdi->b_started) {
 		TSK_DEBUG_INFO("GDI screencast producer already stopped");
 		goto bail;
 	}
 	
 	if (p_gdi->p_timer_mgr) {
 		tsk_timer_manager_stop(p_gdi->p_timer_mgr);
 	}
 	
 	p_gdi->b_started = tsk_false;
 	p_gdi->b_paused = tsk_false;
 
 bail:
 	tsk_safeobj_unlock(p_gdi);
 
 	return 0;
 }
 
 static int _tdav_producer_screencast_grab(tdav_producer_screencast_gdi_t* p_self)
 {
 	int ret = 0;
 	HDC hSrcDC = NULL, hMemDC = NULL;
 	HBITMAP     hBitmap, hOldBitmap;
     int         nWidth, nHeight; 
 	RECT		rcSrc;
 
 	if (!p_self) {
 		TSK_DEBUG_ERROR("Invalid parameter");
 		return -1;
 	}
 
 	tsk_safeobj_lock(p_self);
 
 	if (!p_self->b_started) {
 		TSK_DEBUG_ERROR("producer not started yet");
 		ret = -2;
 		goto bail;
 	}
 
 	if (!TMEDIA_PRODUCER(p_self)->enc_cb.callback) {
 		goto bail;
 	}
 	 
 	hSrcDC = GetDC(p_self->hwnd_src);
 	if (!hSrcDC) {
 		TSK_DEBUG_ERROR("GetDC(%llx) failed", (int64_t)p_self->hwnd_src);
 		ret = -5;
 		goto bail;
 	}
     hMemDC = CreateCompatibleDC(hSrcDC);
 	if (!hMemDC) {
 		TSK_DEBUG_ERROR("CreateCompatibleDC(%llx) failed", (int64_t)hSrcDC);
 		ret = -6;
 		goto bail;
 	}
 
 	// get points of rectangle to grab
 	if (p_self->hwnd_src) {
 		GetWindowRect(p_self->hwnd_src, &rcSrc);
 	}
 	else {
 		rcSrc.left = rcSrc.top = 0;
 		rcSrc.right = GetDeviceCaps(hSrcDC, HORZRES);
 		rcSrc.bottom = GetDeviceCaps(hSrcDC, VERTRES);
 	}
 
     nWidth  = rcSrc.right - rcSrc.left;
     nHeight = rcSrc.bottom - rcSrc.top;	
 
 	p_self->bitmapInfoSrc.bmiHeader.biWidth = nWidth;
 	p_self->bitmapInfoSrc.bmiHeader.biHeight = nHeight;
 	p_self->bitmapInfoSrc.bmiHeader.biSizeImage = nWidth * nHeight * (p_self->bitmapInfoSrc.bmiHeader.biBitCount >> 3);
 
 	// create a bitmap compatible with the screen DC
 #if TDAV_UNDER_WINDOWS_CE
 	{
 		void* pvBits = NULL;
 		hBitmap = CreateDIBSection(hSrcDC, &p_self->bitmapInfoSrc, DIB_RGB_COLORS, &pvBits, NULL, 0);
 		if (!hBitmap || !pvBits) {
 			TSK_DEBUG_ERROR("Failed to create bitmap(%dx%d)", nWidth, nHeight);
 			goto bail;
 		}
 		p_self->p_buff_src = pvBits;
 		p_self->n_buff_src = p_self->bitmapInfoSrc.bmiHeader.biSizeImage;
 	}
 #else
     hBitmap = CreateCompatibleBitmap(hSrcDC, nWidth, nHeight);
 	if (!hBitmap) {
 		TSK_DEBUG_ERROR("Failed to create bitmap(%dx%d)", nWidth, nHeight);
 		goto bail;
 	}
 
 	if (p_self->n_buff_src < p_self->bitmapInfoSrc.bmiHeader.biSizeImage) {
 		if (p_self->p_buff_src) VirtualFree(p_self->p_buff_src, 0, MEM_RELEASE);
 		if (!(p_self->p_buff_src = VirtualAlloc(NULL, p_self->bitmapInfoSrc.bmiHeader.biSizeImage, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE))) {
 			p_self->n_buff_src = 0;
 			ret = -3;
 			goto bail;
 		}
 		p_self->n_buff_src = p_self->bitmapInfoSrc.bmiHeader.biSizeImage;
 	}
 #endif /* TDAV_UNDER_WINDOWS_CE */
 
     // select new bitmap into memory DC
     hOldBitmap = (HBITMAP) SelectObject(hMemDC, hBitmap);
 
     // bitblt screen DC to memory DC
     BitBlt(hMemDC, 0, 0, nWidth, nHeight, hSrcDC, 0, 0, SRCCOPY);
 
     // select old bitmap back into memory DC and get handle to
     // bitmap of the screen   
     hBitmap = (HBITMAP)  SelectObject(hMemDC, hOldBitmap);
 
 	// Copy the bitmap data into the provided BYTE buffer
 #if TDAV_UNDER_WINDOWS_CE
 	// memory already retrieved using "CreateDIBSection"
 #else
     GetDIBits(hSrcDC, hBitmap, 0, nHeight, p_self->p_buff_src, &p_self->bitmapInfoSrc, DIB_RGB_COLORS);
 #endif
 	
 	// resize
 	ResizeRGB(&p_self->bitmapInfoSrc.bmiHeader,
 			(const unsigned char *) p_self->p_buff_src,
 			&p_self->bitmapInfoNeg.bmiHeader,
 			(unsigned char *) p_self->p_buff_neg,
 			p_self->bitmapInfoNeg.bmiHeader.biWidth,
 			p_self->bitmapInfoNeg.bmiHeader.biHeight);
 	
 	// preview
 	if (p_self->hwnd_preview) {
 		HDC hDC = GetDC(p_self->hwnd_preview);
 		if (hDC) {
 			RECT rcPreview = {0};
 			if (GetWindowRect(p_self->hwnd_preview, &rcPreview)) {
 				LONG nPreviewWidth = (rcPreview.right - rcPreview.left);
 				LONG nPreviewHeight = (rcPreview.bottom - rcPreview.top);
 
 				SetStretchBltMode(hDC, COLORONCOLOR);
 #if 0 // preview(neg)
 				StretchDIBits(
 					hDC,
 					0, 0, nPreviewWidth, nPreviewHeight,
 					0, 0, p_self->bitmapInfoNeg.bmiHeader.biWidth, p_self->bitmapInfoNeg.bmiHeader.biHeight,
 					p_self->p_buff_neg,
 					&p_self->bitmapInfoNeg,
 					DIB_RGB_COLORS,
 					SRCCOPY);
 #else // preview(src)
 				StretchDIBits(
 					hDC,
 					0, 0, nPreviewWidth, nPreviewHeight,
 					0, 0, p_self->bitmapInfoSrc.bmiHeader.biWidth, p_self->bitmapInfoSrc.bmiHeader.biHeight,
 					p_self->p_buff_src,
 					&p_self->bitmapInfoSrc,
 					DIB_RGB_COLORS,
 					SRCCOPY);
 #endif 
 			}
 			ReleaseDC(p_self->hwnd_preview, hDC);
 		}
 	}
 
 	// encode and send data
 	TMEDIA_PRODUCER(p_self)->enc_cb.callback(TMEDIA_PRODUCER(p_self)->enc_cb.callback_data, p_self->p_buff_neg, p_self->bitmapInfoNeg.bmiHeader.biSizeImage);
 
 bail:
 	tsk_safeobj_unlock(p_self);
 	
 	if (hSrcDC) {
 		ReleaseDC(p_self->hwnd_src, hSrcDC);
 	}
 	if (hMemDC) {
 		DeleteDC(hMemDC);
 	}
 
 	if (hBitmap) {
         DeleteObject(hBitmap);
 		if (!bitmapBuffSrcOwnMemory) {
 			p_self->p_buff_src = NULL;
 			p_self->n_buff_src = 0;
 		}
 	}
 
 	return ret;
 }
 
 static int _tdav_producer_screencast_timer_cb(const void* arg, tsk_timer_id_t timer_id)
 {
 	tdav_producer_screencast_gdi_t* p_gdi = (tdav_producer_screencast_gdi_t*)arg;
 	int ret = 0;
 
 	tsk_safeobj_lock(p_gdi);
 
 	if (p_gdi->id_timer_grab == timer_id) {
 		if (ret = _tdav_producer_screencast_grab(p_gdi)) {
 			goto bail;
 		}
 		if (p_gdi->b_started) {
 			p_gdi->id_timer_grab = tsk_timer_manager_schedule(p_gdi->p_timer_mgr, p_gdi->u_timout_grab, _tdav_producer_screencast_timer_cb, p_gdi);
 			if (!TSK_TIMER_ID_IS_VALID(p_gdi->id_timer_grab)) {
 				TSK_DEBUG_ERROR("Failed to schedule timer with timeout=%llu", p_gdi->u_timout_grab);
 				ret = -2;
 				goto bail;
 			}
 		}
 	}
 
 bail:
 	tsk_safeobj_unlock(p_gdi);
 	return 0;
 }
 
 //
 //	GDI screencast producer object definition
 //
 /* constructor */
 static tsk_object_t* _tdav_producer_screencast_gdi_ctor(tsk_object_t *self, va_list * app)
 {
 	tdav_producer_screencast_gdi_t *p_gdi = (tdav_producer_screencast_gdi_t *)self;
 	if (p_gdi) {
 		/* init base */
 		tmedia_producer_init(TMEDIA_PRODUCER(p_gdi));
 		TMEDIA_PRODUCER(p_gdi)->video.chroma = tmedia_chroma_bgr24; // RGB24 on x86 (little endians) stored as BGR24
 		/* init self with default values*/
 		TMEDIA_PRODUCER(p_gdi)->video.fps = 15;
 		TMEDIA_PRODUCER(p_gdi)->video.width = 352;
 		TMEDIA_PRODUCER(p_gdi)->video.height = 288;
 
 		tsk_safeobj_init(p_gdi);
 	}
 	return self;
 }
 /* destructor */
 static tsk_object_t* _tdav_producer_screencast_gdi_dtor(tsk_object_t * self)
 { 
 	tdav_producer_screencast_gdi_t *p_gdi = (tdav_producer_screencast_gdi_t *)self;
 	if (p_gdi) {
 		/* stop */
 		if (p_gdi->b_started) {
 			_tdav_producer_screencast_gdi_stop((tmedia_producer_t*)p_gdi);
 		}
 
 		/* deinit base */
 		tmedia_producer_deinit(TMEDIA_PRODUCER(p_gdi));
 		/* deinit self */
 		TSK_OBJECT_SAFE_FREE(p_gdi->p_timer_mgr);
 		if (p_gdi->p_buff_neg) {
 			VirtualFree(p_gdi->p_buff_neg, 0, MEM_RELEASE);
 			p_gdi->p_buff_neg = NULL;
 		}
 		if (p_gdi->p_buff_src) {
 			if (bitmapBuffSrcOwnMemory) {
 				VirtualFree(p_gdi->p_buff_src, 0, MEM_RELEASE);
 			}
 			p_gdi->p_buff_src = NULL;
 		}
 		tsk_safeobj_deinit(p_gdi);
 
 		TSK_DEBUG_INFO("*** GDI Screencast producer destroyed ***");
 	}
 
 	return self;
 }
 /* object definition */
 static const tsk_object_def_t tdav_producer_screencast_gdi_def_s = 
 {
 	sizeof(tdav_producer_screencast_gdi_t),
 	_tdav_producer_screencast_gdi_ctor, 
 	_tdav_producer_screencast_gdi_dtor,
 	tsk_null, 
 };
 /* plugin definition*/
 static const tmedia_producer_plugin_def_t tdav_producer_screencast_gdi_plugin_def_s = 
 {
 	&tdav_producer_screencast_gdi_def_s,
 	tmedia_bfcp_video,
 	"Microsoft GDI screencast producer",
 	
 	_tdav_producer_screencast_gdi_set,
 	_tdav_producer_screencast_gdi_prepare,
 	_tdav_producer_screencast_gdi_start,
 	_tdav_producer_screencast_gdi_pause,
 	_tdav_producer_screencast_gdi_stop
 };
 const tmedia_producer_plugin_def_t *tdav_producer_screencast_gdi_plugin_def_t = &tdav_producer_screencast_gdi_plugin_def_s;
 
 #endif /* TDAV_UNDER_WINDOWS && !TDAV_UNDER_WINDOWS_RT */