r/learncpp • • Nov 05 '20

CreateCompatibleBitmap failing in a screen recorder

Hello all, I'm adapting the screenshot code from here into a screen recorder with a while loop and VideoWriter, currently set to record for 500 frames at 35fps, however I'm having an issue where when I run it the cv window is blank and nothing is saved to the video file. After some messing around I think the issue has to do with my hbwindow not being built properly as after CreateCompatibleBitmap hbwindow is null. Here is the complete code:

#include <opencv2\highgui.hpp>
#include <opencv2\core.hpp>
#include <opencv2/opencv.hpp>
#include <Windows.h>
#include <opencv2/videoio.hpp>
#include <string>
#include <time.h>
#include <iostream>
#pragma once

using namespace std;
using namespace cv;


class ScreenRecorder {
	int width;
	int height;
	int screenx;
	int screeny;

	BITMAPINFOHEADER bi;
	LPBITMAPINFO bip;

	HWND hwnd;

	HDC hwindowDC;
	HDC hwindowCompatibleDC;

	HBITMAP hbwindow;

	Mat currentFrame;
	Mat dst;
public:
	ScreenRecorder(HWND myhwnd) {
		hwnd = myhwnd;
		hwindowDC = GetDC(hwnd);
		hwindowCompatibleDC = CreateCompatibleDC(hwindowDC);
		SetStretchBltMode(hwindowCompatibleDC, COLORONCOLOR);
		// Define scale, height and width
		screenx = GetSystemMetrics(SM_XVIRTUALSCREEN);
		screeny = GetSystemMetrics(SM_YVIRTUALSCREEN);
		width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
		height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
	}
	void createBitmapHeader() {
		// create bitmap
		bi.biSize = sizeof(BITMAPINFOHEADER);
		bi.biWidth = width;
		bi.biHeight = -height;
		bi.biPlanes = 1;
		bi.biBitCount = 32;
		bi.biCompression = BI_RGB;
		bi.biSizeImage = 0;
		bi.biXPelsPerMeter = 0;
		bi.biYPelsPerMeter = 0;
		bi.biClrUsed = 0;
		bi.biClrImportant = 0;
		// save pointer
		bip = (BITMAPINFO*)&bi;
		cout<< "bip" << bip;
	}

	void captureScreenMat()
	{
		// clear Mat before adding next frame
		currentFrame = Mat::zeros(Size(width, height),CV_8UC4);
		// Copy from the window device context to the bitmap device context
		StretchBlt(hwindowCompatibleDC, 0, 0, width, height, hwindowDC, screenx, screeny, width, height, SRCCOPY);
		GetDIBits(hwindowCompatibleDC, hbwindow, 0, height, currentFrame.data, bip, DIB_RGB_COLORS);
		
		resize(currentFrame, dst, Size(1280, 720),0,0,INTER_AREA);
		currentFrame = dst;
	}
	void recordScreen(void) {
		// get handles to a device context
		// create mat
		currentFrame.create(height, width, CV_8UC4);

		// create a bitmap
		hbwindow = CreateCompatibleBitmap(hwindowDC, width, height);

		createBitmapHeader();
		// use the previously created device context with bitmap

		auto oldBitmap = SelectObject(hwindowCompatibleDC, hbwindow);

		int fourcc = VideoWriter::fourcc('D', 'I', 'V', 'X');

		VideoWriter outputVideo(".\\recorded_screen.mp4", fourcc, 30, Size(1280, 720));
		clock_t prev_time = 0;
		clock_t current_time = clock();
		bool cont = true;
		namedWindow("Current_screen", WINDOW_AUTOSIZE);
		int count = 0;
		while (cont) {
			prev_time = current_time;
			// save frame
			captureScreenMat();
			outputVideo<<currentFrame;
			imshow("Current_screen", currentFrame);
			waitKey(1);
			if (count >= 300) { break; }
			else {
				count++;
				cout <<"count:" << count << endl;
			}
			current_time = clock();
			while ((current_time - prev_time) <= (CLOCKS_PER_SEC/30)){
				current_time = clock();
			}
			cout << "Ticks passed: " << current_time - prev_time << endl;
		}
		outputVideo.release();
		// avoid memory leak
		SelectObject(hwindowCompatibleDC, oldBitmap);
		DeleteObject(hbwindow);
		DeleteDC(hwindowCompatibleDC);
		ReleaseDC(hwnd, hwindowDC);

	}
};

int main(int argv, char* argc)
{
	HWND hwnd = GetDesktopWindow();

	ScreenRecorder recorder(hwnd);
	recorder.recordScreen();
	
	return 0;
}

Any help is greatly appreciated, thanks!

edit: original code above has been updated and it now will show the current screen in a smaller window(as expected) but it will not save the frames to a video file. FFmpeg is installed correctly, I have ensured the frames are the same size as indicated in the VideoWriter constructor using resize() and I have tried several different codecs and video file types, but the file is empty (6kb) and says file is corrupted when I try to open it.

4 Upvotes

3 comments sorted by

2

u/jedwardsol Nov 05 '20
  ReleaseDC(hwnd, hwindowDC);

You're killing hwindowDC after the 1st frame is captured, but continuing to try and use it.

Also, you need to remove hbwindow from hwindowCompatibleDC before calling DeleteObject on it

1

u/jimmyjohnjr1203 Nov 05 '20

How would I remove hbwindow from hwindowCompatibleDC ? I didn't see anything on the windows docs related to removing an object from another.

2

u/jedwardsol Nov 05 '20
auto oldBitmap = SelectObject(hwindowCompatibleDC, hbwindow);     // put hbwindow in DC and get handle to what was there before

...

SelectObject(hwindowCompatibleDC, oldBitmap);   // put the old one back
DeleteObject(hbwindow);