OBS框架流程和源码分析七一视频流捕获机制

1. 视频流捕获机制

1.1. 视频流捕获基本原理

所谓视频流,实际上是由一张张图像组成,由于人体眼睛的捕获频率,以及视觉暂留机制,在图像连续播放时,会让大脑以为产生连贯性的动画效果。常见的电影帧率是24帧/秒(24fps),而游戏一般要到60fps(高清)才不会觉得明显卡顿(这个讨论见https://www.zhihu.com/question/21081976/answer/34748080)。也就是,对于电影或者一般游戏而言,按照某个帧率播放,只要按照这个帧率捕获数据,基本可以采集到每一帧完整的数据。
再来一个直观的感受:

  • 帧率在24fps,播放一张图的时间是41.7ms,如果1s播放一张图,那么帧率在1fps。
  • 帧率在30fps,播放一张图的时间是33.3ms
  • 帧率在60fps,播放一张图的时间是16.7ms

1.2. 窗口图像帧捕获

1.2.1. 基本原理

为了取得窗口图像帧,首先得清楚窗口图像帧原理。

大概的原理:
Windows为了提升显示性能,图形相关的操作,都在内核层(win32k.sys),在这里,设计上有两条线:

  • 一条是管理线,在win32k.sys中有专门的窗口管理器,处理窗口的父子层级关系、foreground窗口、焦点信息、各窗口句柄,以及各个消息队列;
  • 一条是渲染线,这里win32k应该是提供了与显示设备驱动交互的功能,通过GDI接口对外暴露出来。应用程序通过调用GDI接口,对窗口绑定的显示缓存进行绘制。最终还是结合管理线中的窗口层级,来做显示的消隐处理。

应用层使用的基本方式,就是创建消息循环,创建窗口及绑定窗口过程函数,通过消息循环派发消息事件给过程函数,去执行不同动作。渲染线最后是通过WM_PAINT消息驱动,去刷新每个窗口自身的显示区,最终触发屏幕对应的FrontBuffer数据展示。这里的每个窗口自身的显示区,windows中用DC来关联,每一个窗口都有一个DC以及一段buffer,实际的操作,都是通过DC来处理这段buffer。我们可以通过DC来获取到窗口内的数据。

窗口管理器负责管理了各个窗口的显示资源buffer地址,应用程序的渲染过程负责对窗口显示buffer资源进行修改。那么获取图像帧,只需要拿到最终修改完成的buffer即可,可通过DC操作来拿。

1.2.2. 窗口捕获代码

void SaveBitmapToFile(HBITMAP hBitMap, LPCWSTR lpstrFileName)
{
   
   
    BITMAP bitmap;
    GetObject(hBitMap, sizeof(BITMAP), &bitmap);

    BITMAPFILEHEADER     bmfHdr;           //位图文件头结构
    BITMAPINFOHEADER     bi;               //位图信息头结构
    LPBITMAPINFOHEADER   lpbi;             //指向位图信息头结构

    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = bitmap.bmWidth;
    bi.biHeight = bitmap.bmHeight;
    bi.biPlanes = 1;
    HDC hDC = CreateDC(L"DISPLAY", NULL, NULL, NULL);
    int iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES);
    DeleteDC(hDC);
    if (iBits <= 1) bi.biBitCount = 1;
    else if (iBits <= 4) bi.biBitCount = 4;
    else if (iBits <= 8) bi.biBitCount = 8;
    else if (iBits <= 24) bi.biBitCount = 24;
    else bi.biBitCount = iBits;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    DWORD dwBmBitsSize = ((bitmap.bmWidth * bi.biBitCount + 31) / 32) * 4 * bitmap.bmHeight;

    // 计算调色板大小
    DWORD dwPaletteSize = 0;
    if (bi.biBitCount <= 8) dwPaletteSize = (1 << bi.biBitCount) * sizeof(RGBQUAD);

    // 设置位图文件头
    bmfHdr.bfType = 0x4D42;     // "BM "
    DWORD dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize;
    bmfHdr.bfSize = dwDIBSize;
    bmfHdr.bfReserved1 = 0;
    bmfHdr.bfReserved2 = 0;
    bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize;

    // 为位图内容分配内存
    HANDLE hDib = GlobalAlloc(GHND, dwBmBitsSize + dwPaletteSize + sizeof(BITMAPINFOHEADER));
    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);
    *lpbi = bi;
    // 处理调色板      
    HPALETTE hPal = (HPALETTE)GetStockObject(DEFAULT_PALETTE);
    HPALETTE hOldPal = NULL;
    if (hPal)
    {
   
   
        hDC = GetDC(NULL);
        hOldPal = SelectPalette(hDC, hPal, FALSE);
        RealizePalette(hDC);
    }
    // 获取该调色板下新的像素值
    GetDIBits(hDC, hBitMap, 0, (UINT)bitmap.bmHeight, (LPSTR)lpbi + sizeof(BITMAPINFOHEADER) + dwPaletteSize, (LPBITMAPINFO)lpbi, DIB_RGB_COLORS);
    // 恢复调色板      
    if (hOldPal)
    {
   
   
        SelectPalette(hDC, hOldPal, TRUE);
        RealizePalette(hDC);
        ReleaseDC(NULL, hDC);
    }

	HANDLE hFile = CreateFile(lpstrFileName, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    
	// 写入位图文件头
    DWORD dwWritten = 0;
	WriteFile(hFile, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
	// 写入位图文件其余内容
	WriteFile(hFile, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL);
    GlobalUnlock(hDib);
    GlobalFree(hDib);
    CloseHandle(hFile);
}

void Capture(HWND hWnd)
{
   
   
    HDC hdc = GetDC(hWnd);

    RECT rcWnd = {
   
    0 };
    GetClientRect(hWnd, &rcWnd);

    int cx = rcWnd.right - rcWnd.left;
    int cy = rcWnd.bottom - rcWnd.top;

    HDC hdcMem = CreateCompatibleDC(hdc);
    HBITMAP bitmap = CreateCompatibleBitmap(hdc, cx, cy)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值