菜鸟学ffmpeg音视频技术之4 使用d3d 抓屏幕图像

使用D3D 9抓取屏幕图像并转为RGB数据
这篇博客介绍了如何利用Direct3D 9捕获屏幕图像并将其转换为RGB数据,以便与FFmpeg进行编解码。详细步骤包括创建D3D对象、获取显示配置、创建设备对象、创建CreateOffscreenPlainSurface对象以及将数据转换为RGB格式。适用于需要使用D3D抓图和FFmpeg录制的场景。

本篇介绍如何使用d3d 9抓取屏幕图像,并将图像转化为RGB数据,转化后的数据可直接传递给FFmpeg进行编解码。

获取桌面数据容易,转为RGB网上的例子就少了,大多只会使用D3D api保存为一个图片,本文解决了这个难题,如果你想使用D3D抓图然后使用FFMPEG进行录制会对你有所帮助。

1.创建D3D对象

_d3d = Direct3DCreate9(D3D_SDK_VERSION);

2.获取display配置

            memset(&_d3d_displaymodel, 0, sizeof(_d3d_displaymodel));
            HRESULT hr = _d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &_d3d_displaymodel);
            if (FAILED(hr))
            {
                SafeRelease(_d3d);
                return error;
            }

3.创建设备对象

            D3DPRESENT_PARAMETERS d3dParam;
            memset(&d3dParam, 0, sizeof(d3dParam));
            d3dParam.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
            d3dParam.MultiSampleType = D3DMULTISAMPLE_NONE;
            d3dParam.SwapEffect = D3DSWAPEFFECT_DISCARD;
            d3dParam.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
            d3dParam.Windowed = TRUE;
            d3dParam.BackBufferWidth = 0;
            d3dParam.BackBufferHeight = 0;
            d3dParam.BackBufferFormat = D3DFMT_UNKNOWN;
            d3dParam.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
            d3dParam.hDeviceWindow = hWnd;


            // create d3ddevice
            hr = _d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dParam, &_d3d_device);
            if (FAILED(hr))
            {
                SafeRelease(_d3d_device);
                return error;
            }
4. 创建CreateOffscreenPlainSurface对象

 hr = _d3d_device->CreateOffscreenPlainSurface(_d3d_displaymodel.Width, _d3d_displaymodel.Height,
                D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &_d3d_surface, NULL);
            if (FAILED(hr))
            {
                SafeRelease(_d3d_device);
                SafeRelease(_d3d_surface);
                return error;
            }

5.获取数据

            _d3d_device->GetFrontBufferData(0, _d3d_surface);
            D3DLOCKED_RECT lr;
            _d3d_surface->LockRect(&lr,NULL,
                               D3DLOCK_NO_DIRTY_UPDATE|
                               D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY);
6.转为为RGB数据

            int BITSPERPIXEL = 32;
            DWORD* pBuf = (DWORD*)lr.pBits;
            int nPitch = lr.Pitch;
            for( int i=0 ; i < winheight ; i++)
            {
                memcpy((BYTE*) _buffer + i *
                    winwidth * BITSPERPIXEL/8 ,
                    (BYTE*) pBuf+ i* nPitch ,
                    winwidth* BITSPERPIXEL/8);
            }
 

注意桌面缓存不使用BackBuffer,所以这里一定要用GetFrontBufferData,GetBackBuffer是拿不到数据的。

 

完整代码:

#ifndef RECORD_WINDOW_D3D
#define RECORD_WINDOW_D3D

#include "record_desktop.h"

#include <Windows.h>
#include <d3d9.h>
#pragma comment(lib,"d3d9.lib")

namespace RecorderSP {


template <typename T>

inline void SafeRelease(T* &p)

{
    if (NULL != p)
    {
        p->Release();
        p = NULL;
    }
}

    class RecordWindowD3D :
        public RecordObject
    {
    public:
        RecordWindowD3D();
        ~RecordWindowD3D();

        virtual int init(
            const Record_Rect &rect,
            const int fps);

        virtual int start();
        virtual int pause();
        virtual int resume();
        virtual int stop();

    protected:
        virtual void clean_up();

    private:
        void draw_cursor(HDC hdc);

        int do_record();

        void do_sleep(int64_t dur, int64_t pre, int64_t now);

        void record_func();

        uint8_t *_buffer;
        uint32_t _buffer_size;
        uint32_t _width, _height;

        std::atomic_bool _draw_cursor;

        HDC _hdc;
        HBITMAP _bmp, _bmp_old;
        CURSORINFO _ci;

        IDirect3D9*         _d3d{NULL};

        IDirect3DDevice9*   _d3d_device{NULL};

        IDirect3DSurface9*  _d3d_surface{NULL};

        D3DDISPLAYMODE      _d3d_displaymodel;
    };

}

#endif

 


/******************************************************************************************源文件*********************************************************************************/

 

#include "record_window_d3d.h"

#include "error_define.h"
#include "log_helper.h"
#include <iostream>

namespace RecorderSP {

    RecordWindowD3D::RecordWindowD3D()
    {
        _data_type = Record_Desktop_Data_Types::AT_DESKTOP_BGRA;
        _buffer = NULL;
        _buffer_size = 0;

        _draw_cursor = true;

        _hdc = NULL;
        _bmp = NULL;
        _bmp_old = NULL;
        _ci = { 0 };
    }

    RecordWindowD3D::~RecordWindowD3D()
    {
        stop();
        clean_up();
    }

    int RecordWindowD3D::init(const Record_Rect & rect, const int fps)
    {
        int error = AE_NO;
        if (_inited == true) {
            return error;
        }

        _d3d = Direct3DCreate9(D3D_SDK_VERSION);

        if (_d3d == NULL)
            return error;

        _fps = fps;
        _rect = rect;

        do {
            _width = rect.right - rect.left;
            _height = rect.bottom - rect.top;
            _buffer_size = (_width * 32 + 31) / 32 * _height * 4;
            _buffer = new uint8_t[_buffer_size];

            _start_time = av_gettime_relative();
            _time_base = { 1,AV_TIME_BASE };
            _pixel_fmt = AV_PIX_FMT_BGRA;


            _inited = true;
        } while (0);

        al_info("init gdi finished,error: %s %ld", err2str(error), GetLastError());

        return error;
    }

    int RecordWindowD3D::start()
    {
        if (_running == true) {
            al_warn("record desktop gdi is already running");
            return AE_NO;
        }

        if (_inited == false) {
            return AE_NEED_INIT;
        }

        _running = true;
        _thread = std::thread(std::bind(&RecordWindowD3D::record_func, this));

        return AE_NO;
    }

    int RecordWindowD3D::pause()
    {
        _paused = true;
        return AE_NO;
    }

    int RecordWindowD3D::resume()
    {
        _paused = false;
        return AE_NO;
    }

    int RecordWindowD3D::stop()
    {
        _running = false;
        if (_thread.joinable())
            _thread.join();

        return AE_NO;
    }

    void RecordWindowD3D::clean_up()
    {
        _inited = false;

        if (_buffer)
            delete[] _buffer;

        _buffer = nullptr;
    }

    void RecordWindowD3D::draw_cursor(HDC hdc)
    {
        if (!(_ci.flags & CURSOR_SHOWING))
            return;

        //is cursor in the tartet zone
        if (_ci.ptScreenPos.x < _rect.left ||
            _ci.ptScreenPos.x > _rect.right ||
            _ci.ptScreenPos.y < _rect.top ||
            _ci.ptScreenPos.y > _rect.bottom
            )
            return;

        HICON icon;
        ICONINFO ii;

        icon = CopyIcon(_ci.hCursor);
        if (!icon)
            return;

        int dstx = 0, dsty = 0;
        dstx = abs(_ci.ptScreenPos.x - _rect.left);
        dsty = abs(_ci.ptScreenPos.y - _rect.top);

        if (GetIconInfo(icon, &ii)) {
            POINT pos;
            DrawIconEx(hdc, dstx, dsty, icon, 0, 0, 0, NULL, DI_NORMAL);

            DeleteObject(ii.hbmColor);
            DeleteObject(ii.hbmMask);
        }

        DestroyIcon(icon);
    }

    int RecordWindowD3D::do_record()
    {

        int error = AE_ERROR;

        do {
            std::string windowname = get_main_record_window();
            HWND hWnd = ::FindWindowA(NULL, windowname.c_str());
            memset(&_d3d_displaymodel, 0, sizeof(_d3d_displaymodel));
            HRESULT hr = _d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &_d3d_displaymodel);
            if (FAILED(hr))
            {
                SafeRelease(_d3d);
                return error;
            }

            //get window rect
            RECT rtWnd;
            ::GetClientRect(hWnd, &rtWnd);
            int winwidth = rtWnd.right-rtWnd.left;
            int winheight = rtWnd.bottom - rtWnd.top;

            //init d3d param
            D3DPRESENT_PARAMETERS d3dParam;
            memset(&d3dParam, 0, sizeof(d3dParam));
            d3dParam.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
            d3dParam.MultiSampleType = D3DMULTISAMPLE_NONE;
            d3dParam.SwapEffect = D3DSWAPEFFECT_DISCARD;
            d3dParam.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
            d3dParam.Windowed = TRUE;
            d3dParam.BackBufferWidth = 0;
            d3dParam.BackBufferHeight = 0;
            d3dParam.BackBufferFormat = D3DFMT_UNKNOWN;
            d3dParam.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
            d3dParam.hDeviceWindow = hWnd;


            // create d3ddevice
            hr = _d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dParam, &_d3d_device);
            if (FAILED(hr))
            {
                SafeRelease(_d3d_device);
                return error;
            }

            IDirect3DSurface9  *pBackBuffer = NULL;
            hr = _d3d_device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pBackBuffer);
            if (FAILED(hr))
            {
                SafeRelease(_d3d_device);
                SafeRelease(_d3d_surface);
                return error;
            }
            hr = _d3d_device->CreateOffscreenPlainSurface(_d3d_displaymodel.Width, _d3d_displaymodel.Height,
                D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &_d3d_surface, NULL);
            if (FAILED(hr))
            {
                SafeRelease(_d3d_device);
                SafeRelease(_d3d_surface);
                return error;
            }
//            D3DSURFACE_DESC surfacedesc;
//            hr = pBackBuffer->GetDesc(&surfacedesc);
//            if (FAILED(hr))
//            {
//                SafeRelease(_d3d_device);
//                SafeRelease(_d3d_surface);
//                SafeRelease(pBackBuffer);
//                return error;
//            }
//            _d3d_device->GetRenderTargetData(pBackBuffer,_d3d_surface);
//            D3DLOCKED_RECT lr;
//            hr = pBackBuffer->LockRect(&lr,NULL, D3DLOCK_NO_DIRTY_UPDATE|D3DLOCK_NOSYSLOCK|
//                                       D3DLOCK_READONLY);
//            if (FAILED(hr))
//            {
//                SafeRelease(_d3d_device);
//                SafeRelease(_d3d_surface);
//                SafeRelease(pBackBuffer);
//                return error;
//            }
            _d3d_device->GetFrontBufferData(0, _d3d_surface);
            D3DLOCKED_RECT lr;
            _d3d_surface->LockRect(&lr,NULL,
                               D3DLOCK_NO_DIRTY_UPDATE|
                               D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY);

            int BITSPERPIXEL = 32;
            DWORD* pBuf = (DWORD*)lr.pBits;
            int nPitch = lr.Pitch;
            for( int i=0 ; i < winheight ; i++)
            {
                memcpy((BYTE*) _buffer + i *
                    winwidth * BITSPERPIXEL/8 ,
                    (BYTE*) pBuf+ i* nPitch ,
                    winwidth* BITSPERPIXEL/8);
            }

             _d3d_surface->UnlockRect();

//            RECT rtCapture;
//            ::GetWindowRect(hWnd, &rtCapture);

//            ID3DXBuffer* pBuffer=NULL;
//            hr = D3DXSaveSurfaceToFileInMemory(pBuffer, D3DXIFF_BMP, _d3d_surface, NULL, rtCapture);
//            if (FAILED(hr))
//            {
//                SafeRelease(_d3d_device);
//                SafeRelease(_d3d_surface);
//                return error;
//            }

            SafeRelease(_d3d_device);
            SafeRelease(_d3d_surface);

//            //draw cursor
//            memset(&_ci, 0, sizeof(CURSORINFO));
//            _ci.cbSize = sizeof(CURSORINFO);
//            if (GetCursorInfo(&_ci)) {
//                draw_cursor(hdc_mem);
//            }

//            BITMAPINFOHEADER   bi;

//            bi.biSize = sizeof(BITMAPINFOHEADER);
//            bi.biWidth = _width;
//            bi.biHeight = _height * (-1);
//            bi.biPlanes = 1;
//            bi.biBitCount = 32;//should get from system color bits
//            bi.biCompression = BI_RGB;
//            bi.biSizeImage = 0;
//            bi.biXPelsPerMeter = 0;
//            bi.biYPelsPerMeter = 0;
//            bi.biClrUsed = 0;
//            bi.biClrImportant = 0;

//            //scan colors by line order
//            int ret = GetDIBits(hdc_mem, hbm_mem, 0, _height, _buffer, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
//            if (ret <= 0 || ret == ERROR_INVALID_PARAMETER) {
//                al_error("get dibits failed:%lu", GetLastError());
//                error = AE_GDI_GET_DIBITS_FAILED;
//                break;
//            }


            error = AE_NO;
        } while (0);


        return  AE_NO;
    }

    void RecordWindowD3D::do_sleep(int64_t dur, int64_t pre, int64_t now)
    {
        int64_t delay = now - pre;
        dur = delay > dur ? max(0, dur - (delay - dur)) : (dur + dur - delay);

        //al_debug("%lld", delay);

        if(dur)
            av_usleep(dur);
    }

    void RecordWindowD3D::record_func()
    {
        AVFrame *frame = av_frame_alloc();

        int64_t pre_pts = 0;
        int64_t dur = AV_TIME_BASE / _fps;

        int ret = AE_NO;
        while (_running)
        {
            ret = do_record();

            if (ret != AE_NO) {
                if (_on_local_error) _on_local_error(ret);
                if (_on_publish_error) _on_publish_error(ret);
                break;
            }

            frame->pts = av_gettime_relative();
            frame->pkt_dts = frame->pts;

            frame->width = _width;
            frame->height = _height;
            frame->format = AV_PIX_FMT_BGRA;
            frame->pict_type = AV_PICTURE_TYPE_I;
            frame->pkt_size = _width * _height * 4;

            av_image_fill_arrays(frame->data,
                frame->linesize,
                _buffer,
                AV_PIX_FMT_BGRA,
                _width,
                _height,
                1
            );

            if (_on_local_data)
                _on_local_data(frame);
            if (_on_publish_data)
                _on_publish_data(frame);

            do_sleep(dur, pre_pts, frame->pts);

            pre_pts = frame->pts;
        }

        av_frame_free(&frame);
    }


}
 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值