Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 10개 있습니다.)
VC++: 121. DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++)
; https://www.sysnet.pe.kr/2/0/11385

.NET Framework: 705. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드
; https://www.sysnet.pe.kr/2/0/11400

.NET Framework: 706. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력
; https://www.sysnet.pe.kr/2/0/11401

.NET Framework: 712. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV
; https://www.sysnet.pe.kr/2/0/11407

.NET Framework: 713. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV (2)
; https://www.sysnet.pe.kr/2/0/11408

.NET Framework: 913. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 라이브러리
; https://www.sysnet.pe.kr/2/0/12238

.NET Framework: 1123. C# - (SharpDX + DXGI) 화면 캡처한 이미지를 빠르게 JPG로 변환하는 방법
; https://www.sysnet.pe.kr/2/0/12889

.NET Framework: 1126. C# - snagit처럼 화면 캡처를 연속으로 수행해 동영상 제작
; https://www.sysnet.pe.kr/2/0/12895

.NET Framework: 1128. C# - 화면 캡처한 이미지를 ffmpeg(FFmpeg.AutoGen)로 동영상 처리
; https://www.sysnet.pe.kr/2/0/12897

.NET Framework: 1152. C# - 화면 캡처한 이미지를 ffmpeg(FFmpeg.AutoGen)로 동영상 처리 (저해상도 현상 해결)
; https://www.sysnet.pe.kr/2/0/12963




C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드

이전 글에서 DXGI를 이용한 화면 캡처 소스 코드를 C++로 알아봤는데요.

DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++)
; https://www.sysnet.pe.kr/2/0/11385

이번엔 C#으로 옮겨봤습니다. 물론 이를 위해 DirectX를 위한 Interop 라이브러리가 필요한데요, 바로 SharpDX가 그런 역할을 합니다.

A new managed .NET/C# Direct3D 11 API generated from DirectX SDK headers 
; http://code4k.blogspot.kr/2010/10/managed-netc-direct3d-11-api-generated.html

NuGet에도 배포되어 있는 데다,

SharpDX
; https://www.nuget.org/packages/SharpDX/

SharpDX.DXGI
; https://www.nuget.org/packages/SharpDX.DXGI/4.1.0-ci184

github에 소스 코드와 그 예제 코드가 모두 공개되어 있습니다. 그중에는 화면 캡처 예제도 있습니다.

SharpDX-Samples/Desktop/Direct3D11.1/ScreenCapture/Program.cs 
; https://github.com/sharpdx/SharpDX-Samples/blob/master/Desktop/Direct3D11.1/ScreenCapture/Program.cs




Nuget을 통해 SharpDX를 참조하면,

Install-Package SharpDX.Direct3D11 -Version 4.0.1 
Install-Package SharpDX.DXGI -Version 4.0.1

각각 다음의 DLL을 얻게 됩니다.

SharpDX.dll
SharpDX.DXGI.dll
SharpDX.Direct3D11.dll

이를 이용해 "DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++)" 글의 DXGIManager, DXGIOutputDuplication 클래스를 각각 C#으로 다음과 같이 작성할 수 있습니다.

// DXGIManager.cs

using SharpDX;
using SharpDX.DXGI;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;

namespace WindowsFormsApp1
{
    public class DXGIManager : IDisposable
    {
        // ...[생략]...

        public DXGIManager(CaptureSource source)
        {
            Initialize(source);
        }

        void Initialize(CaptureSource captureSource)
        {
            _captureSource = captureSource;
            _factory = new Factory4();
            _outputs = new List<DXGIOutputDuplication>();

            int vgaCardCount = _factory.GetAdapterCount();

            foreach (Adapter adapter in _factory.Adapters)
            {
                List<Output> outputs = new List<Output>();

                foreach (Output output in adapter.Outputs)
                {
                    OutputDescription desc = output.Description;
                    if (desc.IsAttachedToDesktop == false)
                    {
                        continue;
                    }

                    outputs.Add(output);
                }

                if (outputs.Count == 0)
                {
                    continue;
                }

                SharpDX.Direct3D11.Device device = new SharpDX.Direct3D11.Device(adapter);

                foreach (Output output in outputs)
                {
                    using (Output1 output1 = output.QueryInterface<Output1>())
                    {
                        OutputDuplication outputDuplication = output1.DuplicateOutput(device);

                        if (outputDuplication == null)
                        {
                            continue;
                        }

                        _outputs.Add(
                            new DXGIOutputDuplication(adapter, device, outputDuplication, output1.Description));
                    }
                }
                // ...[생략]...
            }

            if (this.Initialized == true)
            {
                CalcOutputRect();
            }
        }

        public bool Capture(byte[] buf, int timeout)
        {
            foreach (DXGIOutputDuplication dupOutput in GetOutputDuplicationByCaptureSource())
            {
                Rectangle desktopBounds = dupOutput.DesktopCoordinates;
                if (dupOutput.AcquireNextFrame(timeout, copyBuffer, buf) == false)
                {
                    return false;
                }
            }

            return true;
        }

        private void copyBuffer(Surface1 surface1, Rectangle desktopBounds, byte[] buf)
        {
            if (surface1 == null)
            {
                return;
            }

            DataRectangle map = surface1.Map(MapFlags.Read);

            GCHandle pinnedArray = GCHandle.Alloc(buf, GCHandleType.Pinned);
            IntPtr dstPtr = pinnedArray.AddrOfPinnedObject();
            IntPtr srcPtr = map.DataPointer;

            int height = desktopBounds.Height;
            int width = desktopBounds.Width;

            Rectangle offsetBounds = desktopBounds;
            offsetBounds.Offset(-this._outputRect.Left, -this._outputRect.Top);

            {
                for (int y = 0; y < height; y++)
                {
                    Utilities.CopyMemory(dstPtr + (offsetBounds.Left) * 4, srcPtr, width * 4);

                    srcPtr = IntPtr.Add(srcPtr, map.Pitch);
                    dstPtr = IntPtr.Add(dstPtr, this.Width * 4);
                }
            }

            pinnedArray.Free();
            surface1.Unmap();
        }

        // ...[생략]...

        private List<DXGIOutputDuplication> GetOutputDuplicationByCaptureSource()
        {
            List<DXGIOutputDuplication> list = new List<DXGIOutputDuplication>();
            int nthMonitor = 0;

            foreach (DXGIOutputDuplication output in _outputs)
            {
                switch (_captureSource)
                {
                    case CaptureSource.Monitor1:
                        if (output.IsPrimary() == true)
                        {
                            list.Add(output);
                        }
                        break;

                    case CaptureSource.Monitor2:
                        if (output.IsPrimary() == false)
                        {
                            list.Add(output);
                        }
                        break;

                    case CaptureSource.Monitor3:
                        if (output.IsPrimary() == false)
                        {
                            nthMonitor++;
                        }

                        if (nthMonitor == ((int)CaptureSource.Monitor3) - 1)
                        {
                            list.Add(output);
                        }
                        break;

                    case CaptureSource.Desktop:
                        list.Add(output);
                        break;
                }

                if (_captureSource != CaptureSource.Desktop && list.Count == 1)
                {
                    break;
                }
            }

            return list;
        }

        // ...[생략]...
    }
}

// DXGIManager.cs

using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.Mathematics.Interop;
using System;
using System.Drawing;
using System.Runtime.InteropServices;

namespace WindowsFormsApp1
{
    class DXGIOutputDuplication
    {
        Adapter _adapter;
        SharpDX.Direct3D11.Device _device;
        SharpDX.Direct3D11.DeviceContext _deviceContext;
        OutputDuplication _outputDuplication;
        OutputDescription _description;

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        static extern bool GetMonitorInfo(IntPtr hMonitor, ref MonitorInfoEx lpmi);

        public DXGIOutputDuplication(Adapter adapter,
            SharpDX.Direct3D11.Device device,
            OutputDuplication outputDuplication, OutputDescription description)
        {
            _adapter = adapter;

            _device = device;
            _deviceContext = _device.ImmediateContext;

            _outputDuplication = outputDuplication;

            _description = description;
        }

        // ...[생략]...

        internal bool AcquireNextFrame(int timeout, Action copyAction, byte[] buf)
        {
            OutputDuplicateFrameInformation fi;
            SharpDX.DXGI.Resource desktopResource = null;

            try
            {
                _outputDuplication.AcquireNextFrame(timeout, out fi, out desktopResource);
            }
            catch (SharpDXException e)
            {
                if (e.ResultCode == DXGIError.DXGI_ERROR_ACCESS_LOST)
                {
                    throw;
                }

                return false;
            }

            if (desktopResource == null)
            {
                return false;
            }

            try
            {
                using (Texture2D textureResource = desktopResource.QueryInterface())
                {
                    Texture2DDescription desc = textureResource.Description;

                    Texture2DDescription textureDescription = desc;
                    textureDescription.MipLevels = 1;
                    textureDescription.ArraySize = 1;
                    textureDescription.SampleDescription.Count = 1;
                    textureDescription.SampleDescription.Quality = 0;
                    textureDescription.Usage = ResourceUsage.Staging;
                    textureDescription.BindFlags = 0;
                    textureDescription.CpuAccessFlags = CpuAccessFlags.Read;
                    textureDescription.OptionFlags = ResourceOptionFlags.None;

                    using (Texture2D d3d11Texture2D = new Texture2D(_device, textureDescription))
                    {
                        _device.ImmediateContext.CopyResource(textureResource, d3d11Texture2D);

                        using (Surface1 surface = d3d11Texture2D.QueryInterface())
                        {
                            copyAction(surface, this.DesktopCoordinates, buf);
                            return true;
                        }
                    }
                }
            }
            finally
            {
                if (desktopResource != null)
                {
                    desktopResource.Dispose();
                }

                _outputDuplication.ReleaseFrame();
            }
        }

        // ...[생략]...
    }
}

첨부한 파일은 위의 예제 코드를 모두 포함, 동작하는 프로젝트입니다. 실행하면 윈도우가 하나 뜨는데, 그 윈도우에 포커스를 두고 Ctrl + C키를 누르면 1번 모니터의 화면을 캡처해서 윈도우에 출력합니다. 이렇게!

dxgi_capture_1.png




참고로, 그래픽 카드 제조사 측에서 제공하는 화면 캡처 SDK도 있습니다. (언어는 C++입니다.)

NVIDIA Capture SDK
; https://developer.nvidia.com/capture-sdk

[PDF] NVIDIA CAPTURE SDK PROGRAMMING GUIDE
; http://developer.download.nvidia.com/designworks/capture-sdk/docs/6.1/NVIDIA-Capture-SDK-Programming-Guide.pdf




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 12/14/2017]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2019-12-04 04시05분
[ffdfdsf] 캡쳐시마다 계속 dx초기화하고 변수만들고 초기화시키고 하는건가요?? 속도가 느린것같은데 어떻게하나요..
그리고 다출력했으면 사용한 메모리 릴리즈 해야되는데 어떤거어떤거 해야되나요
[guest]
2019-12-04 08시30분
제가 공유한 소스 코드에서 캡처 시마다 계속 dx를 초기화했나요?
정성태
2019-12-04 11시15분
[ffdfdsf] _captureEvent.Set(); 이것만 타이머에넣고 돌렸습니다.
[guest]
2021-04-16 11시22분
ShareX/ShareX - a free and open source program that lets you capture or record any area of your screen and share it with a single press of a key.
; https://github.com/ShareX/ShareX
정성태
2023-03-14 09시20분
[초보입니다.] 뭔가 궁금한게 생겨서 검색하다보면 여기로 연결됩니다
항상 좋은 자료들에 감사드립니다.
첨부파일로 그대로 캡처를 연속으로 해보면 메모리가 지속적으로 늘어나는데 초보라서 어느부분을 고쳐야할지 몰라서 문의드립니다.
[guest]
2023-03-14 09시40분
얼핏 기억에 ^^ 그랬던 적이 있었지만 아마 그 이후에 수정했을 것입니다. 다음의 글에 등록한 github 소스 코드가 최신이니 그걸 받아서 해보세요.

C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 라이브러리
; https://www.sysnet.pe.kr/2/0/12238
정성태
2023-03-15 01시17분
[초보입니다.] 빠른 답변에 감사드립니다.
깃허브에 있는걸로 하니 메모리 누수가 없습니다.
감사합니다.
[guest]
2023-03-15 01시45분
[초보입니다] 깃허브에 있는코드는 directxView 에 출력후 bitmap파일로 저장하게 되어있던데 directxView 에 출력없이 바로 bitmap를 반환받으려면 어느곳을 수정해야 할지 시간되실때 알려주시면 감사하겠습니다.
처음 접하는 부분이라 모르는게 너무많아 죄송합니다.
[guest]
2023-07-28 01시37분
정성태

... 31  32  33  34  35  36  37  [38]  39  40  41  42  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
12671정성태6/15/202117267오류 유형: 724. Tomcat 실행 시 Failed to initialize connector [Connector[HTTP/1.1-8080]] 오류
12670정성태6/13/20218861.NET Framework: 1071. DLL Surrogate를 이용한 Out-of-process COM 개체에서의 CoInitializeSecurity 문제파일 다운로드1
12669정성태6/11/20218851.NET Framework: 1070. 사용자 정의 GetHashCode 메서드 구현은 C# 9.0의 record 또는 리팩터링에 맡기세요.
12668정성태6/11/202110551.NET Framework: 1069. C# - DLL Surrogate를 이용한 Out-of-process COM 개체 제작파일 다운로드2
12667정성태6/10/20219175.NET Framework: 1068. COM+ 서버 응용 프로그램을 이용해 CoInitializeSecurity 제약 해결파일 다운로드1
12666정성태6/10/20217858.NET Framework: 1067. 별도 DLL에 포함된 타입을 STAThread Main 메서드에서 사용하는 경우 CoInitializeSecurity 자동 호출파일 다운로드1
12665정성태6/9/20219177.NET Framework: 1066. Wslhub.Sdk 사용으로 알아보는 CoInitializeSecurity 사용 제약파일 다운로드1
12664정성태6/9/20217491오류 유형: 723. COM+ PIA 참조 시 "This operation failed because the QueryInterface call on the COM component" 오류
12663정성태6/9/20218939.NET Framework: 1065. Windows Forms - 속성 창의 디자인 설정 지원: 문자열 목록 내에서 항목을 선택하는 TypeConverter 제작파일 다운로드1
12662정성태6/8/20218154.NET Framework: 1064. C# COM 개체를 PIA(Primary Interop Assembly)로써 "Embed Interop Types" 참조하는 방법파일 다운로드1
12661정성태6/4/202118716.NET Framework: 1063. C# - MQTT를 이용한 클라이언트/서버(Broker) 통신 예제 [4]파일 다운로드1
12660정성태6/3/20219811.NET Framework: 1062. Windows Forms - 폼 내에서 발생하는 마우스 이벤트를 자식 컨트롤 영역에 상관없이 수신하는 방법 [1]파일 다운로드1
12659정성태6/2/202111095Linux: 40. 우분투 설치 후 MBR 디스크 드라이브 여유 공간이 인식되지 않은 경우 - Logical Volume Management
12658정성태6/2/20218523Windows: 194. Microsoft Store에 있는 구글의 공식 Youtube App
12657정성태6/2/20219799Windows: 193. 윈도우 패키지 관리자 - winget 설치
12656정성태6/1/20218051.NET Framework: 1061. 서버 유형의 COM+에 적용할 수 없는 Server GC
12655정성태6/1/20217612오류 유형: 722. windbg/sos - savemodule - Fail to read memory
12654정성태5/31/20217595오류 유형: 721. Hyper-V - Saved 상태의 VM을 시작 시 오류 발생
12653정성태5/31/202110226.NET Framework: 1060. 닷넷 GC에 새롭게 구현되는 DPAD(Dynamic Promotion And Demotion for GC)
12652정성태5/31/20218352VS.NET IDE: 164. Visual Studio - Web Deploy로 Publish 시 암호창이 매번 뜨는 문제
12651정성태5/31/20218607오류 유형: 720. PostgreSQL - ERROR: 22P02: malformed array literal: "..."
12650정성태5/17/20217923기타: 82. OpenTabletDriver의 버튼에 더블 클릭을 매핑 및 게임에서의 지원 방법
12649정성태5/16/20219261.NET Framework: 1059. 세대 별 GC(Garbage Collection) 방식에서 Card table의 사용 의미 [1]
12648정성태5/16/20217899사물인터넷: 66. PC -> FTDI -> NodeMCU v1 ESP8266 기기를 UART 핀을 연결해 직렬 통신하는 방법파일 다운로드1
12647정성태5/15/20219130.NET Framework: 1058. C# - C++과의 연동을 위한 구조체의 fixed 배열 필드 사용파일 다운로드1
12646정성태5/15/20218262사물인터넷: 65. C# - Arduino IDE의 Serial Monitor 기능 구현파일 다운로드1
... 31  32  33  34  35  36  37  [38]  39  40  41  42  43  44  45  ...