截屏,Windows下C++,BitBlt,GetDibits,测试约每秒截25张图片(即普通视频)的程序, 对象是仙剑3外传问情篇
#include "kensin_common.h" //用到里面的时间类,时间类后面会贴,所以可以不用包含
#include "kensin_convert.h" //用到里面的一个BGR24转I420的方法,有公式,可以自己实现
#include <windows.h>
#include <stdio.h>
#include <conio.h>
int main(int argc, char * argv[])
{
// 为Sleep服务,记得系统默认最小单位比较大,比例15ms,Sleep 1-15ms大概都相当于Sleep(15),
// 不过调过timeSetEvent之类的也会改变这个参数
::timeBeginPeriod(1);
Sleep(4000); // 为下面GetForegroundWindow服务
HWND hwnd = ::GetForegroundWindow(); // 得到活动的窗体
RECT rect;
::GetWindowRect(hwnd, &rect);
int w = rect.right - rect.left;
int h = rect.bottom - p;
// 下面这段查MSDN的BitBlt函数,后面有一个 Capturing an Image,大致有下面一段
HDC hdc = ::GetWindowDC(hwnd);
HDC hdcCompatible = ::CreateCompatibleDC(hdc);
HBITMAP hbm = ::CreateCompatibleBitmap(hdc, w, h);
::SelectObject(hdcCompatible, hbm);
unsigned char * data = new unsigned char[w*h*4];
unsigned char * data1 = new unsigned char[w*h*3/2];
// 给GetDIBits用的结构体
BITMAPINFO infoHeader;
memset(&infoHeader.bmiHeader, 0, sizeof(BITMAPINFOHEADER));
infoHeader.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
infoHeader.bmiHeader.biWidth = w;
infoHeader.bmiHeader.biHeight = h;
infoHeader.bmiHeader.biPlanes = 1;
infoHeader.bmiHeader.biBitCount = 32; // 这里建议用32,经过测试在我的机器上比24速度快比较多
infoHeader.bmiHeader.biCompression = BI_RGB;
/
/ 把从窗体上拿到的数据存到文件
FILE * outfile = fopen("d:\\what1", "wb");
int dd, tt;
int type = 2;
//fwrite(&w, 4, 1, outfile);
//fwrite(&h, 4, 1, outfile);
//fwrite(&type, 4, 1, outfile);
int max = 0, max1 = 0;
int count = 600; // 截的图片张数,600大致24秒
kensin::KsTime kstime, kstime1; // 时间辅助类
int pred = -1;
int mm = 0;
while (count--)
{
SendMessage(hwnd, WM_SETREDRAW, FALSE, 0); // 特殊。。。
// 把窗体上的数据弄到备份的hdcCompatible, 由于HBITMAP hbm和hdcCompatible关联
// 相当于hbm里存到了数据,只会只要从hbm里要出数据
::BitBlt(hdcCompatible, 0, 0, w, h, hdc, 0, 0, SRCCOPY);
tt = (int)(Elapsed() * 1000);
if (pred == -1)
pred = tt;
if (tt-pred >max)
max = tt-pred;
pred = tt;
SendMessage(hwnd, WM_SETREDRAW, TRUE, 0); // 对应上面的SendMessage
// 从hbm要出数据,存在data里
::GetDIBits(hdcCompatible, hbm, 0, h, data, &infoHeader, DIB_RGB_COLORS);
// 上面infoHeader.bmiHeader.biBitCount值是32,所以出来的数据一个像素占4个自己,顺序是BGRA
// 下面两行转成BGR24
for (int i = 0; i < w*h; i++)
memmove(data + i*3, data + i*4, 3);
// 数据是倒着的,下面倒回来
for (int i = 0; i < h/2; i++)
{
memcpy(data1, data + i*w*3, w*3);
memcpy(data + i*w*3, data + (h-i-1)*w*3, w*3);
memcpy(data + (h-i-1)*w*3, data1, w*3);
}
// 转成I420的,因为我弄了个开源的x264,里面能编YUV4:2:0的数据,所以弄成I420的了
// 下面方法可以自己实现,公式
/********************************************************************************
*
* Here YUV mean YCbCr, and the formula with RGB is :
*
* Y = 0.257*R' + 0.504*G' + 0.098*B' + 16
* Cb = -0.148*R' - 0.291*G' + 0.439*B' + 128
* Cr = 0.439*R' - 0.368*G' - 0.071*B' + 128
*
* R' = 1.164*(Y - 16) + 1.596*(Cr - 128 )
* G' = 1.164*(Y - 16) - 0.813*(Cr - 128 )- 0.392*( Cb - 128 )
* B' = 1.164*(Y - 16) + 2.017*(Cb - 128 )
*
********************************************************************************/
kensin::ks_cvrt_BGR24toI420(data, data1, w, h);
/
/fwrite(&tt, 4, 1, outfile);
// 把数据写入文件,这里最好另起线程写
fwrite(data1, 1, w*h*3/2, outfile);
// 计算用时,决定需要睡眠多少时间
dd = (int)(Elapsed() * 1000);
if (max1 < dd)
max1 = dd;
mm += dd;
if (dd < 40)
Sleep(40 - dd);
}
printf("%f\n", (double)mm/600);
printf("%d\n", max1);
// 清理工作
fflush(outfile);
fclose(outfile);
::DeleteObject(hbm);
::DeleteDC(hdcCompatible);
::ReleaseDC(hwnd, hdc);
delete[] data;
delete[] data1;
printf("%d\n", max);
:
:timeEndPeriod(1); // 对应timeBeginPeriod
_getch();
return 0;
}
时间辅助类
namespace kensin
{
/********************************************************************************
* 简单的计算时间间隔辅助类
********************************************************************************/
class KsTime
{
private:
__int64 m_base, m_start, m_stop;
bool m_print;
public:
KsTime(bool e_print = false):m_print(e_print)
{
QueryPerformanceFrequency((LARGE_INTEGER *)(&m_base));
restart();
}
~KsTime() { if (m_print) elapsed(); }
void restart() { QueryPerformanceCounter((LARGE_INTEGER *)(&m_start)); }
void elapsed()
{
QueryPerformanceCounter((LARGE_INTEGER *)(&m_stop));
printf("%.6f\n", (double)(m_stop - m_start) / m_base);
}
double getElapsed()
{
QueryPerformanceCounter((LARGE_INTEGER *)(&m_stop));
return (double)(m_stop - m_start) / m_base;
}
void setPrint(bool e_print) { m_print = e_print; }
};
}
测试的起因是我在玩仙剑三外传,突然想着是不是可以每秒截取几十张图片,然后编码形成视频文件呢。
所以首先想起截图,首先有一个问题是,以前用windows自带的Print Screen按钮可以截取整个屏幕,但是
有一些播放器的视频图片截取不到,是黑的,据说是显示用
的Direct Draw加速(可以:运行->输入dxdiag->显示->禁止掉)
虽说可以禁用,但一般游戏可能禁用就玩不了,果然测试了一下,禁用了游戏打不开,虽然可以截取到播放器的图片了。
然后没禁用的情况下打开游戏,发现windows自带的截屏可以截出图片,不是黑的!!!有希望
自然想到程序里用BitBlt和GetDibits联合使用可以做到抓窗体的图片,虽然知道DirextX可能更好更强大,可惜暂时不会。
写了个抓数据存成bmp格式的程序测试,发现果真不是黑的。
#include <windows.h>
#include <stdio.h>
int main(int argc, char * argv[])
{
unsigned char * data = new unsigned char[1024*768*16];
Sleep(4000);
HWND hwnd = ::GetForegroundWindow();
//HWND hwnd = ::GetDesktopWindow();//::GetForegroundWindow();
RECT rect;
::GetWindowRect(hwnd, &rect);
HDC hdc = ::GetWindowDC(hwnd);
int w = rect.right - rect.left;//::GetDeviceCaps(hdc, HORZRES);
int h = rect.bottom - p;//::GetDeviceCaps(hdc, VERTRES);
HDC hdcCompatible = ::CreateCompatibleDC(hdc);
HBITMAP hbm = ::CreateCompatibleBitmap(hdc, w, h);
::SelectObject(hdcCompatible, hbm);
BITMAPINFO infoHeader;
memset(&infoHeader, 0, sizeof(BITMAPINFO));
infoHeader.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
infoHeader.bmiHeader.biWidth = w;
infoHeader.bmiHeader.biHeight = h;
infoHeader.bmiHeader.biPlanes = 1;
infoHeader.bmiHeader.biBitCount = 24;
截屏infoHeader.bmiHeader.biCompression = BI_RGB;
BITMAPFILEHEADER fileHeader;
fileHeader.bfType = (WORD)'B' | ((WORD)'M' << 8);
fileHeader.bfSize = 54 + w*h*3;
fileHeader.bfReserved1 = fileHeader.bfReserved2 = 0;
fileHeader.bfOffBits = 54;
int count = 10;
char name[160] = "d:\\what000.bmp";
while (count--)
{
//::BitBlt(hdcCompatible, 0, 0, w, h, hdc, 0, 0, SRCCOPY);
BOOL b = ::PrintWindow(hwnd, hdcCompatible, 0);
::GetDIBits(hdcCompatible, hbm, 0, h, data, &infoHeader, DIB_RGB_COLORS);
name[9] = (char)(20 - count + 'A');
FILE * outfile = fopen(name, "wb");
fwrite(&fileHeader, sizeof(fileHeader), 1, outfile);
fwrite(&infoHeader.bmiHeader, sizeof(infoHeader.bmiHeader), 1, outfile);
fwrite(data, 1, w*h*3, outfile);
fflush(outfile);
fclose(outfile);
Sleep(100);
}
::DeleteObject(hbm);
::DeleteDC(hdcCompatible);
::ReleaseDC(hwnd, hdc);
delete[] data;
return 0;
}
然后就写了上面那个程序,主要担心时间上可能处理不了,毕竟40ms要处理一轮
开始infoHeader.bmiHeader.biBitCount=24,那自己的屏幕测试,分辨率(1280x1024,我的显示器到极限了)
关了DirectDraw加速,开了一个动画片,
发现BitBlt耗时比较少<2ms,但是GetDiBits耗时好的时候15ms左右,多数时候大于40ms(上面计算时间差的辅助类计算精度是比较可靠的)
这样显然不行,查网上据说有一个GetBitmapBits的方法比较快,但是查MSDN里说是比较老
的系统用的,测试了下速度
比GetDibits还真要快一些。
但毕竟感觉不太好,时间上也接近越过20ms了,后来发现GetBitmapBits出来的数据是每个像素4个字节
然后infoHeader.bmiHeader.biBitCount=32测了一下,结果是老的东西能不用就不要用了,这里还有一个好处是每行是4的倍数,
不用刻意对齐(windows下的bmp每行像素占的字节数需要是4的倍数)
BitBlt + GetDiBits的速度对应截自己的屏幕基本上的过的去(15ms左右,仅指我的机器,显卡比较新,CPU4核的,当然这里是单线程的)
然后测试仙三外,发现BitBlt耗时40多毫秒,GetDiBits几乎不耗时
这样的话相当于程序基本上在执行BitBlt,和游戏共用一个hdc,而且不Sleep,游戏毫无疑问的卡。。。。
游戏是把我的屏幕分辨率重新弄成了1024x768,全屏显示(它支持两种1024x768 和800x600)
这样的话肯定弄不下去了。。。。
。。。。。。。
也确实停顿了很长时间
后来人品比较好,瞎改了一下设置发现问题解决了,在游戏设置里把一个叫“垂直同步”的关掉了,就变好了,至于为什么。。不知道
游戏也完全可以正常玩
然后兴奋的截取了20几秒的数据,I420的都近700M了,写了个测试播放原数据的程序一看,闪,很闪。
然后存了一些bmp图片发现有些图片不全,没房子没人的,这样的图片间断的放,反差很大,就形成了闪。
截自己屏幕的时候没闪,而且看到的动画也很流畅。。。。想不通
最后觉得应该是游戏的画面重绘了一半被我取出来造成的,毕竟游戏一半是一秒60张图片,
最后到让窗体不重绘的方法,就是程序里的SendMessage(hwnd, WM_SETREDRAW, FALSE, 0);
只有BitBlt和窗体有关联,所以调用完了就马上调用SendMessage(hwnd, WM_SETREDRAW, TRUE, 0);否则游戏玩不了
这样还有期望这样不要给游戏造成什么影响
然后就是测试了。。。。
结果:不理想
问题1:有SendMessage后时间平均上升了不少,虽然最后平均时间能压在40ms内(我的机器可以),
但是很不稳定,时不时会来个很大的间差时间,直接造成视频停顿了一下
问题2:有SendMessage后,游戏鼠标附近闪动的很快,有一个附加的鼠标。。。,然后概率性游戏会最小化,最小化
的时候SendMessage慢的不行。。。。
问题3:写文件很不稳定,时不时也会来个几百毫秒写w*h*3/2个字节,好在这个可以另起一个线程搞定,比较写文件
耗的平均时间是够用的,且它慢不是很耗CPU,现在单核的机器不多见,所以这个应该不是大问题
主要是问题2让人很难受。。。。,当然是SendMessage加入后造成的,但它确实让视频不闪了。
不知道哪位仁兄仁能给个好的建
议
上面由于结果不理想,所以没有整理成函数的形式,这种适合的对象是一般窗体都可以
同样的SetDiBits和BitBlt联合使用可以把图像显示到窗体上去,代码和上面类似。
然后Windows API里面还有一个DrawDibDraw可以显示图片(当然视频就是很多图片)
OVER
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论