diff options
Diffstat (limited to 'tinyDAV/src/video/gdi/tdav_producer_screencast_gdi.c')
-rw-r--r-- | tinyDAV/src/video/gdi/tdav_producer_screencast_gdi.c | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/tinyDAV/src/video/gdi/tdav_producer_screencast_gdi.c b/tinyDAV/src/video/gdi/tdav_producer_screencast_gdi.c new file mode 100644 index 0000000..799aafc --- /dev/null +++ b/tinyDAV/src/video/gdi/tdav_producer_screencast_gdi.c @@ -0,0 +1,534 @@ +/* Copyright (C) 2014-2015 Mamadou DIOP. +* +* 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_thread.h" +#include "tsk_memory.h" +#include "tsk_safeobj.h" +#include "tsk_timer.h" +#include "tsk_time.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 */ + +// https://social.msdn.microsoft.com/forums/windowsdesktop/en-us/2cbe4674-e744-41d6-bc61-3c8e381aa942/how-to-make-bitblt-faster-for-copying-screen +#if !defined(HIGH_PRIO_BITBLIT) +# define HIGH_PRIO_BITBLIT 0 +#endif /* HIGH_PRIO_BITBLIT */ + +typedef struct tdav_producer_screencast_gdi_s +{ + TMEDIA_DECLARE_PRODUCER; + + HWND hwnd_preview; + HWND hwnd_src; + + BITMAPINFO bitmapInfoSrc; + BITMAPINFO bitmapInfoNeg; + + tsk_thread_handle_t* tid[1]; + + 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 void* TSK_STDCALL _tdav_producer_screencast_record_thread(void *arg); +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 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; + + TSK_DEBUG_INFO("[GDI screencast] fps:%d, width:%d; height:%d", TMEDIA_PRODUCER(p_gdi)->video.fps, TMEDIA_PRODUCER(p_gdi)->video.width, TMEDIA_PRODUCER(p_gdi)->video.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); + } + +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; + } + + p_gdi->b_started = tsk_true; + + tsk_thread_create(&p_gdi->tid[0], _tdav_producer_screencast_record_thread, p_gdi); +#if HIGH_PRIO_BITBLIT + if (p_gdi->tid[0]) { + tsk_thread_set_priority(p_gdi->tid[0], TSK_THREAD_PRIORITY_TIME_CRITICAL); + } +#endif + +bail: + if (ret) { + p_gdi->b_started = tsk_false; + } + 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; + } + + p_gdi->b_started = tsk_false; + p_gdi->b_paused = tsk_false; + + // stop thread + if (p_gdi->tid[0]) { + tsk_thread_join(&(p_gdi->tid[0])); + } + +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(%x) failed", (int64_t)p_self->hwnd_src); + ret = -5; + goto bail; + } + hMemDC = CreateCompatibleDC(hSrcDC); + if (!hMemDC) { + TSK_DEBUG_ERROR("CreateCompatibleDC(%x) 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 void* TSK_STDCALL _tdav_producer_screencast_record_thread(void *arg) +{ + tdav_producer_screencast_gdi_t* p_gdi = (tdav_producer_screencast_gdi_t*)arg; + int ret = 0; + + // FPS manager + uint64_t TimeNow, TimeLastFrame = 0; + const uint64_t TimeFrameDuration = (1000 / TMEDIA_PRODUCER(p_gdi)->video.fps); + + TSK_DEBUG_INFO("_tdav_producer_screencast_record_thread -- START"); + + while (ret == 0 && p_gdi->b_started) { + TimeNow = tsk_time_now(); + if ((TimeNow - TimeLastFrame) >= TimeFrameDuration) { + if (!p_gdi->b_muted && !p_gdi->b_paused) { + if (ret = _tdav_producer_screencast_grab(p_gdi)) { + goto next; + } + } + TimeLastFrame = TimeNow; + } + else { + tsk_thread_sleep(1); +#if 0 + TSK_DEBUG_INFO("[GDI screencast] Skip frame"); +#endif + } + next: + ; + } + TSK_DEBUG_INFO("_tdav_producer_screencast_record_thread -- STOP"); + return tsk_null; +} + +// +// 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 */ + 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 */ |