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분
정성태

... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...
NoWriterDateCnt.TitleFile(s)
1303정성태6/26/201227412개발 환경 구성: 152. sysnet DB를 SQL Azure 데이터베이스로 마이그레이션
1302정성태6/25/201229483개발 환경 구성: 151. Azure 웹 사이트에 사용자 도메인 네임 연결하는 방법
1301정성태6/20/201225770오류 유형: 156. KB2667402 윈도우 업데이트 실패 및 마이크로소프트 Answers 웹 사이트 대응
1300정성태6/20/201231804.NET Framework: 329. C# - Rabin-Miller 소수 생성방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 [1]파일 다운로드2
1299정성태6/18/201232907제니퍼 .NET: 21. 제니퍼 닷넷 - Ninject DI 프레임워크의 성능 분석 [2]파일 다운로드2
1298정성태6/14/201234422VS.NET IDE: 72. Visual Studio에서 pfx 파일로 서명한 경우, 암호는 어디에 저장될까? [2]
1297정성태6/12/201231064VC++: 63. 다른 프로세스에 환경 변수 설정하는 방법파일 다운로드1
1296정성태6/5/201227711.NET Framework: 328. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 - 두 번째 이야기 [4]파일 다운로드1
1295정성태6/5/201225094.NET Framework: 327. RSAParameters와 System.Numerics.BigInteger 이야기파일 다운로드1
1294정성태5/27/201248556.NET Framework: 326. 유니코드와 한글 - 유니코드와 닷넷을 이용한 한글 처리 [7]파일 다운로드2
1293정성태5/24/201229784.NET Framework: 325. System.Drawing.Bitmap 데이터를 Parallel.For로 처리하는 방법 [2]파일 다운로드1
1292정성태5/24/201223765.NET Framework: 324. First-chance exception에 대해 조건에 따라 디버거가 멈추게 할 수는 없을까? [1]파일 다운로드1
1291정성태5/23/201230297VC++: 62. 배열 초기화를 위한 기계어 코드 확인 [2]
1290정성태5/18/201235087.NET Framework: 323. 관리자 권한이 필요한 작업을 COM+에 대행 [7]파일 다운로드1
1289정성태5/17/201239248.NET Framework: 322. regsvcs.exe로 어셈블리 등록 시 시스템 변경 사항 [5]파일 다운로드2
1288정성태5/17/201226470.NET Framework: 321. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (3) - Type Library파일 다운로드1
1287정성태5/17/201229308.NET Framework: 320. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (2) - .NET 4.0 + .NET 2.0 [2]
1286정성태5/17/201238248.NET Framework: 319. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (1) - .NET 2.0 + x86/x64/AnyCPU [5]
1285정성태5/16/201233281.NET Framework: 318. gacutil.exe로 어셈블리 등록 시 시스템 변경 사항파일 다운로드1
1284정성태5/15/201225714오류 유형: 155. Windows Phone 연결 상태에서 DRIVER POWER STATE FAILURE 블루 스크린 뜨는 현상
1283정성태5/12/201233326.NET Framework: 317. C# 관점에서의 Observer 패턴 구현 [1]파일 다운로드1
1282정성태5/12/201226118Phone: 6. Windows Phone 7 Silverlight에서 Google Map 사용하는 방법 [3]파일 다운로드1
1281정성태5/9/201233204.NET Framework: 316. WPF/Silverlight의 그래픽 단위와 Anti-aliasing 처리를 이해하자 [1]파일 다운로드1
1280정성태5/9/201226165오류 유형: 154. Could not load type 'System.ServiceModel.Activation.HttpModule' from assembly 'System.ServiceModel, ...'.
1279정성태5/9/201224927.NET Framework: 315. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 [1]파일 다운로드1
1278정성태5/8/201226152오류 유형: 153. Visual Studio 디버깅 - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...