첨부 소스 코드는 나눔고딕코딩 폰트를 사용합니다.
728x90
반응형
728x170

TestSolution.zip
다운로드
TestSolution.z01
다운로드
TestSolution.z02
다운로드
TestSolution.z03
다운로드
TestSolution.z04
다운로드

[TestLibrary 프로젝트]

▶ BITMAPINFOHEADER.cs

using System.Runtime.InteropServices;

namespace TestLibrary
{
    /// <summary>
    /// 비트맵 정보 헤더
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct BITMAPINFOHEADER
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region Field

        /// <summary>
        /// 크기
        /// </summary>
        public uint Size;

        /// <summary>
        /// 너비
        /// </summary>
        public int Width;

        /// <summary>
        /// 높이
        /// </summary>
        public int Height;

        /// <summary>
        /// 플레인 수
        /// </summary>
        public ushort PlaneCount;

        /// <summary>
        /// 비트 수
        /// </summary>
        public ushort BitCount;

        /// <summary>
        /// 압축 방법
        /// </summary>
        public uint Compression;

        /// <summary>
        /// 이미지 크기
        /// </summary>
        public uint ImageSize;

        /// <summary>
        /// 미터당 X 픽셀 수
        /// </summary>
        public int XPixelCountPerMeter;

        /// <summary>
        /// 미터당 Y 픽셀 수
        /// </summary>
        public int YPixelCountPerMeter;

        /// <summary>
        /// 사용 색상 인덱스 수
        /// </summary>
        public uint UsedColorCount;

        /// <summary>
        /// 중료 색상 인덱스 수
        /// </summary>
        public uint ImportantColorIndexCount;

        #endregion
    }
}

 

728x90

 

▶ DirectShowException.cs

using System;
using System.Runtime.InteropServices;

namespace TestLibrary
{
    /// <summary>
    /// DirectShow 예외
    /// </summary>
    public sealed class DirectShowException : Exception
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - DirectShowException(message, resultHandle)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="message">메시지</param>
        /// <param name="resultHandle">결과 핸들</param>
        public DirectShowException(string message, int resultHandle) : base(message, Marshal.GetExceptionForHR(resultHandle))
        {
        }

        #endregion
    }
}

 

300x250

 

▶ VideoCaptureDevice.cs

using System;

namespace TestLibrary
{
    /// <summary>
    /// 비디오 캡처 장치
    /// </summary>
    public sealed class VideoCaptureDevice : IEquatable<VideoCaptureDevice>
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 명칭
        /// </summary>
        private readonly String name;

        /// <summary>
        /// 장치 경로
        /// </summary>
        private readonly String devicePath;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 명칭 - Name

        /// <summary>
        /// 명칭
        /// </summary>
        public string Name
        {
            get
            {
                return this.name;
            }
        }

        #endregion
        #region 장치 경로 - DevicePath

        /// <summary>
        /// 장치 경로
        /// </summary>
        internal string DevicePath
        {
            get
            {
                return this.devicePath;
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - VideoCaptureDevice(info)

        /// <summary>
        /// 생성자
        /// </summary>
        public VideoCaptureDevice(VideoInputDeviceInfo info)
        {
            this.name = info.DeviceName;
            devicePath = info.DevicePath;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 연산자 == 재정의하기 - ==(left, right)

        /// <summary>
        /// 연산자 == 재정의하기
        /// </summary>
        /// <param name="left">왼쪽 객체</param>
        /// <param name="right">오른쪽 객체</param>
        /// <returns>처리 결과</returns>
        public static bool operator ==(VideoCaptureDevice left, VideoCaptureDevice right)
        {
            return Equals(left, right);
        }

        #endregion
        #region 연산자 != 재정의하기 - !=(left, right)

        /// <summary>
        /// 연산자 != 재정의하기
        /// </summary>
        /// <param name="left">왼쪽 객체</param>
        /// <param name="right">오른쪽 객체</param>
        /// <returns>처리 결과</returns>
        public static bool operator !=(VideoCaptureDevice left, VideoCaptureDevice right)
        {
            return !Equals(left, right);
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 동일 여부 구하기 - Equals(sourceObject)

        /// <summary>
        /// 동일 여부 구하기
        /// </summary>
        /// <param name="sourceObject">소스 객체</param>
        /// <returns>동일 여부</returns>
        public override bool Equals(object sourceObject)
        {
            if(ReferenceEquals(null, sourceObject))
            {
                return false;
            }

            if(ReferenceEquals(this, sourceObject))
            {
                return true;
            }

            if(sourceObject.GetType() != typeof(VideoCaptureDevice))
            {
                return false;
            }

            return Equals((VideoCaptureDevice)sourceObject);
        }

        #endregion
        #region 동일 여부 구하기 - Equals(source)

        /// <summary>
        /// 동일 여부 구하기
        /// </summary>
        /// <param name="source">소스 객체</param>
        /// <returns>동일 여부</returns>
        public bool Equals(VideoCaptureDevice source)
        {
            if(ReferenceEquals(null, source))
            {
                return false;
            }

            if(ReferenceEquals(this, source))
            {
                return true;
            }

            return Equals(source.name, this.name) && Equals(source.devicePath, this.devicePath);
        }

        #endregion
        #region 해시 코드 구하기 - GetHashCode()

        /// <summary>
        /// 해시 코드 구하기
        /// </summary>
        /// <returns>해시 코드</returns>
        public override int GetHashCode()
        {
            unchecked
            {
                return (this.name.GetHashCode() * 397) ^ this.devicePath.GetHashCode();
            }
        }

        #endregion
    }
}

 

▶ VideoInputDeviceInfo.cs

using System.Runtime.InteropServices;

namespace TestLibrary
{
    /// <summary>
    /// 비디오 입력 장치 정보
    /// </summary>
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct VideoInputDeviceInfo
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region Field

        /// <summary>
        /// 장치명
        /// </summary>
        [MarshalAs(UnmanagedType.BStr)]
        public string DeviceName;

        /// <summary>
        /// 장치 경로
        /// </summary>
        [MarshalAs(UnmanagedType.BStr)]
        public string DevicePath;

        #endregion
    }
}

 

▶ DirectShowHelper.cs

using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;

namespace TestLibrary
{
    /// <summary>
    /// DirectShow 헬퍼
    /// </summary>
    public sealed class DirectShowHelper : IDisposable
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Import
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 라이브러리 로드하기 - LoadLibrary(filePath)

        /// <summary>
        /// 라이브러리 로드하기
        /// </summary>
        /// <param name="filePath">파일 경로</param>
        /// <returns>처리 결과</returns>
        [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern IntPtr LoadLibrary(string filePath);

        #endregion
        #region 프로시저 주소 구하기 - GetProcAddress(moduleHandle, procedureName)

        /// <summary>
        /// 프로시저 주소 구하기
        /// </summary>
        /// <param name="moduleHandle">모듈 핸들</param>
        /// <param name="procedureName">프로시저명</param>
        /// <returns>프로세스 핸들</returns>
        [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
        private static extern IntPtr GetProcAddress(IntPtr moduleHandle, string procedureName);

        #endregion
        #region 라이브러리 해제하기 - FreeLibrary(moduleHandle)

        /// <summary>
        /// 라이브러리 해제하기
        /// </summary>
        /// <param name="moduleHandle">모듈 핸들</param>
        /// <returns>처리 결과</returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool FreeLibrary(IntPtr moduleHandle);

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Delegate
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 비디오 입력 장치 열거 콜백 처리하기 대리자 - EnumerateVideoInputDeviceCallbackDelegate(info)

        /// <summary>
        /// 비디오 입력 장치 열거 콜백 처리하기 대리자
        /// </summary>
        /// <param name="info">비디오 입력 장치 정보</param>
        public delegate void EnumerateVideoInputDeviceCallbackDelegate(ref VideoInputDeviceInfo info);

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 비디오 입력 장치 리스트 열거하기 대리자 - EnumerateVideoInputDeviceListDelegate(callback)

        /// <summary>
        /// 비디오 입력 장치 리스트 열거하기 대리자
        /// </summary>
        /// <param name="callback">비디오 입력 장치 열거 콜백 대리자</param>
        private delegate void EnumerateVideoInputDeviceListDelegate(EnumerateVideoInputDeviceCallbackDelegate callback);

        #endregion
        #region 캡처 그래프 구축하기 대리자 - BuildCaptureGraphDelegate()

        /// <summary>
        /// 캡처 그래프 구축하기 대리자
        /// </summary>
        /// <returns>처리 결과</returns>
        private delegate int BuildCaptureGraphDelegate();

        #endregion
        #region 렌더 필터 추가하기 대리자 - AddRenderFilterDelegate(windowHandle)

        /// <summary>
        /// 렌더 필터 추가하기 대리자
        /// </summary>
        /// <param name="windowHandle">윈도우 핸들</param>
        /// <returns>처리 결과</returns>
        private delegate int AddRenderFilterDelegate(IntPtr windowHandle);

        #endregion
        #region 캡처 필터 추가하기 대리자 - AddCaptureFilterDelegate(devicePath)

        /// <summary>
        /// 캡처 필터 추가하기 대리자
        /// </summary>
        /// <param name="devicePath">장치 경로</param>
        /// <returns>처리 결과</returns>
        private delegate int AddCaptureFilterDelegate([MarshalAs(UnmanagedType.BStr)]string devicePath);

        #endregion
        #region 캡처 그래프 리셋하기 대리자 - ResetCaptureGraphDelegate()

        /// <summary>
        /// 캡처 그래프 리셋하기 대리자
        /// </summary>
        /// <returns>처리 결과</returns>
        private delegate int ResetCaptureGraphDelegate();

        #endregion
        #region 시작하기 대리자 - StartDelegate()

        /// <summary>
        /// 시작하기 대리자
        /// </summary>
        /// <returns>처리 결과</returns>
        private delegate int StartDelegate();

        #endregion
        #region 현재 이미지 구하기 대리자 - GetCurrentImageDelegate(dibHandle)

        /// <summary>
        /// 현재 이미지 구하기 대리자
        /// </summary>
        /// <param name="dibHandle">DIB 핸들</param>
        /// <returns>처리 결과</returns>
        private delegate int GetCurrentImageDelegate([Out] out IntPtr dibHandle);

        #endregion
        #region 비디오 크기 구하기 대리자 - GetVideoSizeDelegate(width, height)

        /// <summary>
        /// 비디오 크기 구하기 대리자
        /// </summary>
        /// <param name="width">너비</param>
        /// <param name="height">높이</param>
        /// <returns>처리 결과</returns>
        private delegate int GetVideoSizeDelegate(out int width, out int height);

        #endregion
        #region 중단하기 대리자 - StopDelegate()

        /// <summary>
        /// 중단하기 대리자
        /// </summary>
        /// <returns>처리 결과</returns>
        private delegate int StopDelegate();

        #endregion
        #region 캡처 그래프 제거하기 대리자 - DestroyCaptureGraphDelegate()

        /// <summary>
        /// 캡처 그래프 제거하기 대리자
        /// </summary>
        private delegate void DestroyCaptureGraphDelegate();

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 비디오 입력 장치 리스트 열거하기 대리자
        /// </summary>
        private EnumerateVideoInputDeviceListDelegate enumerateVideoInputDeviceListDelegate;

        /// <summary>
        /// 캡처 그래프 구축하기 대리자
        /// </summary>
        private BuildCaptureGraphDelegate buildCaptureGraphDelegate;

        /// <summary>
        /// 렌더 필터 추가하기 대리자
        /// </summary>
        private AddRenderFilterDelegate addRenderFilterDelegate;

        /// <summary>
        /// 캡처 필터 추가하기 대리자
        /// </summary>
        private AddCaptureFilterDelegate addCaptureFilterDelegate;

        /// <summary>
        /// 캡처 그래프 리셋하기 대리자
        /// </summary>
        private ResetCaptureGraphDelegate resetCaptureGraphDelegate;

        /// <summary>
        /// 시작하기 대리자
        /// </summary>
        private StartDelegate startDelegate;

        /// <summary>
        /// 현재 이미지 구하기 대리자
        /// </summary>
        private GetCurrentImageDelegate getCurrentImageDelegate;

        /// <summary>
        /// 비디오 크기 구하기 대리자
        /// </summary>
        private GetVideoSizeDelegate getVideoSizeDelegate;

        /// <summary>
        /// 중단하기 대리자
        /// </summary>
        private StopDelegate stopDelegate;

        /// <summary>
        /// 캡처 그래프 제거하기 대리자
        /// </summary>
        private DestroyCaptureGraphDelegate destroyCaptureGraphDelegate;

        /// <summary>
        /// 모듈 파일 경로
        /// </summary>
        private string moduleFilePath = string.Empty;

        /// <summary>
        /// 모듈 핸들
        /// </summary>
        private IntPtr moduleHandle = IntPtr.Zero;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 32비트 플랫폼 여부 - IsX86Platform

        /// <summary>
        /// 32비트 플랫폼 여부
        /// </summary>
        private bool IsX86Platform
        {
            get
            {
                return IntPtr.Size == 4;
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - DirectShowHelper()

        /// <summary>
        /// 생성자
        /// </summary>
        public DirectShowHelper()
        {
            LoadModule();

            SetDelegate(this.moduleHandle);
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 비디오 입력 장치 리스트 열거하기 - EnumerateVideoInputDeviceList(enumerateVideoInputDeviceCallbackDelegate)

        /// <summary>
        /// 비디오 입력 장치 리스트 열거하기
        /// </summary>
        /// <param name="enumerateVideoInputDeviceCallbackDelegate">비디오 입력 장치 열거 콜백 처리하기 대리자</param>
        public void EnumerateVideoInputDeviceList(EnumerateVideoInputDeviceCallbackDelegate enumerateVideoInputDeviceCallbackDelegate)
        {
            this.enumerateVideoInputDeviceListDelegate(enumerateVideoInputDeviceCallbackDelegate);
        }

        #endregion

        #region 캡처 그래프 구축하기 - BuildCaptureGraph()

        /// <summary>
        /// 캡처 그래프 구축하기
        /// </summary>
        public void BuildCaptureGraph()
        {
            ThrowExceptionForResult(this.buildCaptureGraphDelegate(), "Failed to build a video capture graph.");
        }

        #endregion
        #region 렌더 필터 추가하기 - AddRenderFilter(windowHandle)

        /// <summary>
        /// 렌더 필터 추가하기
        /// </summary>
        /// <param name="windowHandle"></param>
        public void AddRenderFilter(IntPtr windowHandle)
        {
            ThrowExceptionForResult(this.addRenderFilterDelegate(windowHandle), "Failed to setup a render filter.");
        }

        #endregion
        #region 캡처 필터 추가하기 - AddCaptureFilter(devicePath)

        /// <summary>
        /// 캡처 필터 추가하기
        /// </summary>
        /// <param name="devicePath">장치 경로</param>
        public void AddCaptureFilter(string devicePath)
        {
            ThrowExceptionForResult(this.addCaptureFilterDelegate(devicePath), "Failed to add a video capture filter.");
        }

        #endregion
        #region 캡처 그래프 리셋하기 - ResetCaptureGraph()

        /// <summary>
        /// 캡처 그래프 리셋하기
        /// </summary>
        public void ResetCaptureGraph()
        {
            ThrowExceptionForResult(this.resetCaptureGraphDelegate(), "Failed to reset a video capture graph.");
        }

        #endregion
        #region 시작하기 - Start()

        /// <summary>
        /// 시작하기
        /// </summary>
        public void Start()
        {
            ThrowExceptionForResult(this.startDelegate(), "Failed to run a capture graph.");
        }

        #endregion
        #region 현재 이미지 구하기 - GetCurrentImage()

        /// <summary>
        /// 현재 이미지 구하기
        /// </summary>
        /// <returns>현재 이미지</returns>
        public Bitmap GetCurrentImage()
        {
            IntPtr dibHandle;

            ThrowExceptionForResult(this.getCurrentImageDelegate(out dibHandle), "Failed to get the current image.");

            try
            {
                BITMAPINFOHEADER bitmapInfoHeader = (BITMAPINFOHEADER)Marshal.PtrToStructure(dibHandle, typeof(BITMAPINFOHEADER));

                int stride = bitmapInfoHeader.Width * (bitmapInfoHeader.BitCount / 8);

                int padding = stride % 4 > 0 ? 4 - stride % 4 : 0;

                stride += padding;

                PixelFormat pixelFormat = PixelFormat.Undefined;

                switch(bitmapInfoHeader.BitCount)
                {
                    case 1  : pixelFormat = PixelFormat.Format1bppIndexed; break;
                    case 4  : pixelFormat = PixelFormat.Format4bppIndexed; break;
                    case 8  : pixelFormat = PixelFormat.Format8bppIndexed; break;
                    case 16 : pixelFormat = PixelFormat.Format16bppRgb555; break;
                    case 24 : pixelFormat = PixelFormat.Format24bppRgb;    break;
                    case 32 : pixelFormat = PixelFormat.Format32bppRgb;    break;
                }

                Bitmap bitmap = new Bitmap
                (
                    bitmapInfoHeader.Width,
                    bitmapInfoHeader.Height,
                    stride,
                    pixelFormat,
                    (IntPtr)(dibHandle.ToInt64() + Marshal.SizeOf(bitmapInfoHeader))
                );

                bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);

                return bitmap;
            }
            finally
            {
                if(dibHandle != IntPtr.Zero)
                {
                    Marshal.FreeCoTaskMem(dibHandle);
                }
            }
        }

        #endregion
        #region 비디오 크기 구하기 - GetVideoSize()

        /// <summary>
        /// 비디오 크기 구하기
        /// </summary>
        /// <returns>비디오 크기</returns>
        public Size GetVideoSize()
        {
            int width;
            int height;

            ThrowExceptionForResult(this.getVideoSizeDelegate(out width, out height), "Failed to get the video size.");

            return new Size(width, height);
        }

        #endregion
        #region 중단하기 - Stop()

        /// <summary>
        /// 중단하기
        /// </summary>
        public void Stop()
        {
            ThrowExceptionForResult(this.stopDelegate(), "Failed to stop a video capture graph.");
        }

        #endregion
        #region 캡처 그래프 제거하기 - DestroyCaptureGraph()

        /// <summary>
        /// 캡처 그래프 제거하기
        /// </summary>
        public void DestroyCaptureGraph()
        {
            this.destroyCaptureGraphDelegate();
        }

        #endregion

        #region 리소스 해제하기 - Dispose()

        /// <summary>
        /// 리소스 해제하기
        /// </summary>
        public void Dispose()
        {
            if(this.moduleHandle != IntPtr.Zero)
            {
                FreeLibrary(this.moduleHandle);

                this.moduleHandle = IntPtr.Zero;
            }

            if(File.Exists(this.moduleFilePath))
            {
                File.Delete(this.moduleFilePath);
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 모듈 로드하기 - LoadModule()

        /// <summary>
        /// 모듈 로드하기
        /// </summary>
        private void LoadModule()
        {
            string currentDirectoryPath = Environment.CurrentDirectory;

            this.moduleFilePath = Path.Combine
            (
                currentDirectoryPath,
                Environment.Is64BitProcess ? "X64\\DirectShowFacade64.dll" : "X86\\DirectShowFacade.dll"
            );

            this.moduleHandle = LoadLibrary(this.moduleFilePath);

            if(this.moduleHandle == IntPtr.Zero)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }

        #endregion
        #region 대리자 설정하기 - SetDelegate(modlueHandle)

        /// <summary>
        /// 대리자 설정하기
        /// </summary>
        /// <param name="modlueHandle">모듈 핸들</param>
        private void SetDelegate(IntPtr modlueHandle)
        {
            IntPtr methodHandle = GetProcAddress(modlueHandle, "EnumVideoInputDevices");

            this.enumerateVideoInputDeviceListDelegate = (EnumerateVideoInputDeviceListDelegate)Marshal.GetDelegateForFunctionPointer
            (
                methodHandle,
                typeof(EnumerateVideoInputDeviceListDelegate)
            );

            methodHandle = GetProcAddress(modlueHandle, "BuildCaptureGraph");

            this.buildCaptureGraphDelegate = (BuildCaptureGraphDelegate)Marshal.GetDelegateForFunctionPointer
            (
                methodHandle,
                typeof(BuildCaptureGraphDelegate)
            );

            methodHandle = GetProcAddress(modlueHandle, "AddRenderFilter");

            this.addRenderFilterDelegate = (AddRenderFilterDelegate)Marshal.GetDelegateForFunctionPointer
            (
                methodHandle,
                typeof(AddRenderFilterDelegate)
            );

            methodHandle = GetProcAddress(modlueHandle, "AddCaptureFilter");

            this.addCaptureFilterDelegate = (AddCaptureFilterDelegate)Marshal.GetDelegateForFunctionPointer
            (
                methodHandle,
                typeof(AddCaptureFilterDelegate)
            );

            methodHandle = GetProcAddress(modlueHandle, "ResetCaptureGraph");

            this.resetCaptureGraphDelegate = (ResetCaptureGraphDelegate)Marshal.GetDelegateForFunctionPointer
            (
                methodHandle,
                typeof(ResetCaptureGraphDelegate)
            );

            methodHandle = GetProcAddress(modlueHandle, "Start");

            this.startDelegate = (StartDelegate)Marshal.GetDelegateForFunctionPointer
            (
                methodHandle,
                typeof(StartDelegate)
            );

            methodHandle = GetProcAddress(modlueHandle, "GetCurrentImage");

            this.getCurrentImageDelegate = (GetCurrentImageDelegate)Marshal.GetDelegateForFunctionPointer
            (
                methodHandle,
                typeof(GetCurrentImageDelegate)
            );

            methodHandle = GetProcAddress(modlueHandle, "GetVideoSize");

            this.getVideoSizeDelegate = (GetVideoSizeDelegate)Marshal.GetDelegateForFunctionPointer
            (
                methodHandle,
                typeof(GetVideoSizeDelegate)
            );

            methodHandle = GetProcAddress(modlueHandle, "Stop");

            this.stopDelegate = (StopDelegate)Marshal.GetDelegateForFunctionPointer
            (
                methodHandle,
                typeof(StopDelegate)
            );

            methodHandle = GetProcAddress(modlueHandle, "DestroyCaptureGraph");

            this.destroyCaptureGraphDelegate = (DestroyCaptureGraphDelegate)Marshal.GetDelegateForFunctionPointer
            (
                methodHandle,
                typeof(DestroyCaptureGraphDelegate)
            );
        }

        #endregion
        #region 결과용 예외 던지기 - ThrowExceptionForResult(resultHandle, message)

        /// <summary>
        /// 결과용 예외 던지기
        /// </summary>
        /// <param name="resultHandle">결과 핸들</param>
        /// <param name="message">메시지</param>
        private static void ThrowExceptionForResult(int resultHandle, string message)
        {
            if(resultHandle < 0)
            {
                throw new DirectShowException(message, resultHandle);
            }
        }

        #endregion
    }
}

 

▶ FFMpegHelper.cs

using System;
using System.IO;
using System.Runtime.InteropServices;

using FFmpeg.AutoGen;

namespace TestLibrary
{
    /// <summary>
    /// FFMPEG 헬퍼
    /// </summary>
    public static class FFMpegHelper
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Import
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region DLL 디렉토리 설정하기 - SetDllDirectory(directoryPath)

        /// <summary>
        /// DLL 디렉토리 설정하기
        /// </summary>
        /// <param name="directoryPath">디렉토리 경로</param>
        /// <returns>처리 결과</returns>
        [DllImport("kernel32", SetLastError = true)]
        private static extern bool SetDllDirectory(string directoryPath);

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// LD_LIBRARY_PATH
        /// </summary>
        private const string LD_LIBRARY_PATH = "LD_LIBRARY_PATH";

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 등록하기 - Register()

        /// <summary>
        /// 등록하기
        /// </summary>
        public static void Register()
        {
            switch(Environment.OSVersion.Platform)
            {
                case PlatformID.Win32NT      :
                case PlatformID.Win32S       :
                case PlatformID.Win32Windows :
                {
                    string currentDirectoryPath = Environment.CurrentDirectory;

                    while(currentDirectoryPath != null)
                    {
                        string dllDirectoryPath = Path.Combine(currentDirectoryPath, Environment.Is64BitProcess ? "X64" : "X86");

                        if(Directory.Exists(dllDirectoryPath))
                        {
                            Register(dllDirectoryPath);

                            return;
                        }

                        currentDirectoryPath = Directory.GetParent(currentDirectoryPath)?.FullName;
                    }

                    break;
                }
                case PlatformID.Unix   :
                case PlatformID.MacOSX :
                {
                    string dllDirectoryPath = Environment.GetEnvironmentVariable(LD_LIBRARY_PATH);

                    Register(dllDirectoryPath);

                    break;
                }
            }
        }

        #endregion

        #region 에러 메시지 구하기 - GetErrorMessage(errorCode)

        /// <summary>
        /// 에러 메시지 구하기
        /// </summary>
        /// <param name="errorCode">에러 코드</param>
        /// <returns>에러 메시지</returns>
        public static unsafe string GetErrorMessage(int errorCode)
        {
            int bufferSize = 1024;

            byte* buffer = stackalloc byte[bufferSize];

            ffmpeg.av_strerror(errorCode, buffer, (ulong)bufferSize);

            string message = Marshal.PtrToStringAnsi((IntPtr)buffer);

            return message;
        }

        #endregion
        #region 에러시 예외 던지기 - ThrowExceptionIfError(error)

        /// <summary>
        /// 에러시 예외 던지기
        /// </summary>
        /// <param name="errorCode">에러 코드</param>
        /// <returns>에러 코드</returns>
        public static int ThrowExceptionIfError(this int errorCode)
        {
            if(errorCode < 0)
            {
                throw new ApplicationException(GetErrorMessage(errorCode));
            }

            return errorCode;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Private

        #region 등록하기 - Register(dllDirectoryPath)

        /// <summary>
        /// 등록하기
        /// </summary>
        /// <param name="dllDirectoryPath">DLL 디렉토리 경로</param>
        private static void Register(string dllDirectoryPath)
        {
            switch(Environment.OSVersion.Platform)
            {
                case PlatformID.Win32NT      :
                case PlatformID.Win32S       :
                case PlatformID.Win32Windows :

                    SetDllDirectory(dllDirectoryPath);

                    break;

                case PlatformID.Unix   :
                case PlatformID.MacOSX :

                    string currentValue = Environment.GetEnvironmentVariable(LD_LIBRARY_PATH);

                    if(string.IsNullOrWhiteSpace(currentValue) == false && currentValue.Contains(dllDirectoryPath) == false)
                    {
                        string newValue = currentValue + Path.PathSeparator + dllDirectoryPath;

                        Environment.SetEnvironmentVariable(LD_LIBRARY_PATH, newValue);
                    }

                    break;
            }
        }

        #endregion
    }
}

 

▶ VideoStreamDecoder.cs

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows;

using FFmpeg.AutoGen;

namespace TestLibrary
{
    /// <summary>
    /// 비디오 스트림 디코더
    /// </summary>
    public sealed unsafe class VideoStreamDecoder : IDisposable
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 포맷 컨텍스트
        /// </summary>
        private readonly AVFormatContext* formatContext;

        /// <summary>
        /// 스트림 인덱스
        /// </summary>
        private readonly int streamIndex;

        /// <summary>
        /// 코덱 컨텍스트
        /// </summary>
        private readonly AVCodecContext* codecContext;

        /// <summary>
        /// 패킷
        /// </summary>
        private readonly AVPacket* packet;

        /// <summary>
        /// 프레임
        /// </summary>
        private readonly AVFrame* frame;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 코덱명 - CodecName

        /// <summary>
        /// 코덱명
        /// </summary>
        public string CodecName { get; }

        #endregion
        #region 프레임 크기 - FrameSize

        /// <summary>
        /// 프레임 크기
        /// </summary>
        public Size FrameSize { get; }

        #endregion
        #region 픽셀 포맷 - PixelFormat

        /// <summary>
        /// 픽셀 포맷
        /// </summary>
        public AVPixelFormat PixelFormat { get; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - VideoStreamDecoder(device)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="device">장치</param>
        public VideoStreamDecoder(string device)
        {
            this.formatContext = ffmpeg.avformat_alloc_context();

            AVFormatContext* formatContext = this.formatContext;

            ffmpeg.avdevice_register_all();

            AVInputFormat* inputFormat = ffmpeg.av_find_input_format("dshow");

            ffmpeg.avformat_open_input(&formatContext, device, inputFormat, null).ThrowExceptionIfError();
            
            ffmpeg.avformat_find_stream_info(this.formatContext, null).ThrowExceptionIfError();

            AVStream* stream = null;

            for(var i = 0; i < this.formatContext->nb_streams; i++)
            {
                if(this.formatContext->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO)
                {
                    stream = this.formatContext->streams[i];

                    break;
                }
            }

            if(stream == null)
            {
                throw new InvalidOperationException("Could not found video stream.");
            }

            this.streamIndex = stream->index;

            this.codecContext = stream->codec;

            AVCodecID codecID = this.codecContext->codec_id;

            AVCodec* codec = ffmpeg.avcodec_find_decoder(codecID);

            if(codec == null)
            {
                throw new InvalidOperationException("Unsupported codec.");
            }

            ffmpeg.avcodec_open2(this.codecContext, codec, null).ThrowExceptionIfError();

            CodecName = ffmpeg.avcodec_get_name(codecID);

            FrameSize = new Size(this.codecContext->width, this.codecContext->height);

            PixelFormat = this.codecContext->pix_fmt;

            this.packet = ffmpeg.av_packet_alloc();

            this.frame = ffmpeg.av_frame_alloc();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 컨텍스트 정보 딕셔너리 구하기 - GetContextInfoDictionary()

        /// <summary>
        /// 컨텍스트 정보 딕셔너리 구하기
        /// </summary>
        /// <returns>컨텍스트 정보 딕셔너리</returns>
        public IReadOnlyDictionary<string, string> GetContextInfoDictionary()
        {
            AVDictionaryEntry* dictionaryEntry = null;

            Dictionary<string, string> resultDictionary = new Dictionary<string, string>();

            while((dictionaryEntry = ffmpeg.av_dict_get(this.formatContext->metadata, "", dictionaryEntry, ffmpeg.AV_DICT_IGNORE_SUFFIX)) != null)
            {
                string key   = Marshal.PtrToStringAnsi((IntPtr)dictionaryEntry->key  );
                string value = Marshal.PtrToStringAnsi((IntPtr)dictionaryEntry->value);

                resultDictionary.Add(key, value);
            }

            return resultDictionary;
        }

        #endregion
        #region 다음 프레임 디코드 시도하기 - TryDecodeNextFrame(frame)

        /// <summary>
        /// 다음 프레임 디코드 시도하기
        /// </summary>
        /// <param name="frame">프레임</param>
        /// <returns>처리 결과</returns>
        public bool TryDecodeNextFrame(out AVFrame frame)
        {
            ffmpeg.av_frame_unref(this.frame);

            int errorCode;

            do
            {
                try
                {
                    do
                    {
                        errorCode = ffmpeg.av_read_frame(this.formatContext, this.packet);

                        if(errorCode == ffmpeg.AVERROR_EOF)
                        {
                            frame = *this.frame;

                            return false;
                        }

                        errorCode.ThrowExceptionIfError();
                    }
                    while(this.packet->stream_index != this.streamIndex);

                    ffmpeg.avcodec_send_packet(this.codecContext, this.packet).ThrowExceptionIfError();
                }
                finally
                {
                    ffmpeg.av_packet_unref(this.packet);
                }

                errorCode = ffmpeg.avcodec_receive_frame(this.codecContext, this.frame);
            }
            while(errorCode == ffmpeg.AVERROR(ffmpeg.EAGAIN));

            errorCode.ThrowExceptionIfError();

            frame = *this.frame;

            return true;
        }

        #endregion

        #region 리소스 해제하기 - Dispose()

        /// <summary>
        /// 리소스 해제하기
        /// </summary>
        public void Dispose()
        {
            ffmpeg.av_frame_unref(this.frame);

            ffmpeg.av_free(this.frame);

            ffmpeg.av_packet_unref(this.packet);

            ffmpeg.av_free(this.packet);

            ffmpeg.avcodec_close(this.codecContext);

            AVFormatContext* formatContext = this.formatContext;

            ffmpeg.avformat_close_input(&formatContext);
        }

        #endregion
    }
}

 

▶ VideoFrameConverter.cs

using System;
using System.Runtime.InteropServices;
using System.Windows;

using FFmpeg.AutoGen;

namespace TestLibrary
{
    /// <summary>
    /// 비디오 프레임 컨버터
    /// </summary>
    public sealed unsafe class VideoFrameConverter : IDisposable
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 타겟 크기
        /// </summary>
        private readonly Size targetSize;

        /// <summary>
        /// 컨텍스트
        /// </summary>
        private readonly SwsContext* context;

        /// <summary>
        /// 버퍼 핸들
        /// </summary>
        private readonly IntPtr buferHandle;

        /// <summary>
        /// 임시 프레임 데이터
        /// </summary>
        private readonly byte_ptrArray4 temporaryFrameData;

        /// <summary>
        /// 임시 프레임 라인 크기
        /// </summary>
        private readonly int_array4 temporaryFrameLineSize;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - VideoFrameConverter(sourceSize, sourcePixelFormat, targetSize, targetPixelFormat)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="sourceSize">소스 크기</param>
        /// <param name="sourcePixelFormat">소스 픽셀 포맷</param>
        /// <param name="targetSize">타겟 크기</param>
        /// <param name="targetPixelFormat">타겟 픽셀 포맷</param>
        public VideoFrameConverter(Size sourceSize, AVPixelFormat sourcePixelFormat, Size targetSize, AVPixelFormat targetPixelFormat)
        {
            this.targetSize = targetSize;

            this.context = ffmpeg.sws_getContext
            (
                (int)sourceSize.Width,
                (int)sourceSize.Height,
                sourcePixelFormat,
                (int)targetSize.Width,
                (int)targetSize.Height,
                targetPixelFormat,
                ffmpeg.SWS_FAST_BILINEAR,
                null,
                null,
                null
            );

            if(this.context == null)
            {
                throw new ApplicationException("Could not initialize the conversion context.");
            }

            int bufferSize = ffmpeg.av_image_get_buffer_size(targetPixelFormat, (int)targetSize.Width, (int)targetSize.Height, 1);

            this.buferHandle = Marshal.AllocHGlobal(bufferSize);

            this.temporaryFrameData = new byte_ptrArray4();

            this.temporaryFrameLineSize = new int_array4();

            ffmpeg.av_image_fill_arrays
            (
                ref this.temporaryFrameData,
                ref this.temporaryFrameLineSize,
                (byte*)this.buferHandle,
                targetPixelFormat,
                (int)targetSize.Width,
                (int)targetSize.Height,
                1
            );
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 변환하기 - Convert(sourceFrame)

        /// <summary>
        /// 변환하기
        /// </summary>
        /// <param name="sourceFrame">소스 프레임</param>
        /// <returns>프레임</returns>
        public AVFrame Convert(AVFrame sourceFrame)
        {
            ffmpeg.sws_scale
            (
                this.context,
                sourceFrame.data,
                sourceFrame.linesize,
                0,
                sourceFrame.height,
                this.temporaryFrameData,
                this.temporaryFrameLineSize
            );

            byte_ptrArray8 targetFrameData = new byte_ptrArray8();

            targetFrameData.UpdateFrom(this.temporaryFrameData);

            int_array8 targetFrameLineSize = new int_array8();

            targetFrameLineSize.UpdateFrom(this.temporaryFrameLineSize);

            return new AVFrame
            {
                data     = targetFrameData,
                linesize = targetFrameLineSize,
                width    = (int)this.targetSize.Width,
                height   = (int)this.targetSize.Height
            };
        }

        #endregion
        #region 리소스 해제하기 - Dispose()

        /// <summary>
        /// 리소스 해제하기
        /// </summary>
        public void Dispose()
        {
            Marshal.FreeHGlobal(this.buferHandle);

            ffmpeg.sws_freeContext(this.context);
        }

        #endregion
    }
}

 

▶ CameraControl.xaml

<UserControl x:Class="TestLibrary.CameraControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Image Name="image"
        Stretch="Fill"
        SnapsToDevicePixels="True" />
</UserControl>

 

▶ CameraControl.xaml.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

using FFmpeg.AutoGen;

namespace TestLibrary
{
    /// <summary>
    /// 카메라 컨트롤
    /// </summary>
    public partial class CameraControl : UserControl
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// DirectShow 헬퍼
        /// </summary>
        private static DirectShowHelper _directShowHelper = new DirectShowHelper();

        /// <summary>
        /// 장치명 리스트
        /// </summary>
        private static readonly List<string> _deviceNameList = new List<string>();

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 디스패처
        /// </summary>
        private Dispatcher dispatcher = Application.Current.Dispatcher;

        /// <summary>
        /// 장치명
        /// </summary>
        private string deviceName = null;

        /// <summary>
        /// 스레드
        /// </summary>
        private Thread thread;

        /// <summary>
        /// 스레드 실행 여부
        /// </summary>
        private bool isThreadRunning;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 장치명 리스트 - DeviceNameList

        /// <summary>
        /// 장치명 리스트
        /// </summary>
        public static List<string> DeviceNameList
        {
            get
            {
                return _deviceNameList;
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 장치명 - DeviceName

        /// <summary>
        /// 장치명
        /// </summary>
        public string DeviceName
        {
            get
            {
                return this.deviceName;
            }
            set
            {
                this.deviceName = value;
            }
        }

        #endregion
        #region 캡처 여부 - IsCapturing

        /// <summary>
        /// 캡처 여부
        /// </summary>
        public bool IsCapturing
        {
            get
            {
                return this.isThreadRunning;
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Static

        #region 생성자 - CameraControl()

        /// <summary>
        /// 생성자
        /// </summary>
        static CameraControl()
        {
            FFMpegHelper.Register();
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - CameraControl()

        /// <summary>
        /// 생성자
        /// </summary>
        public CameraControl()
        {
            InitializeComponent();

        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 장치명 리스트 구하기 - GetDeviceNameList()

        /// <summary>
        /// 장치명 리스트 구하기
        /// </summary>
        /// <returns>장치명 리스트</returns>
        public static List<string> GetDeviceNameList()
        {
            _deviceNameList.Clear();
            
            _directShowHelper.EnumerateVideoInputDeviceList(AddVideoCaptureDeviceList);

            return _deviceNameList;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Private

        #region 비디오 캡처 장치 리스트 추가하기 - AddVideoCaptureDeviceList(info)

        /// <summary>
        /// 비디오 캡처 장치 리스트 추가하기
        /// </summary>
        /// <param name="info">비디오 입력 장치 정보</param>
        private static void AddVideoCaptureDeviceList(ref VideoInputDeviceInfo info)
        {
            if(!string.IsNullOrEmpty(info.DevicePath))
            {
                VideoCaptureDevice device = new VideoCaptureDevice(info);

                _deviceNameList.Add(device.Name);
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 시작하기 - Start()

        /// <summary>
        /// 시작하기
        /// </summary>
        public void Start()
        {
            if(this.isThreadRunning == true)
            {
                return;
            }

            if(this.deviceName == null || !_deviceNameList.Contains(this.deviceName))
            {
                return;
            }

            this.isThreadRunning = true;

            this.thread = new Thread(ProcessThread);

            this.thread.Start();
        }

        #endregion
        #region 중단하기 - Stop()

        /// <summary>
        /// 중단하기
        /// </summary>
        public void Stop()
        {
            this.isThreadRunning = false;

            if(this.thread != null && this.thread.IsAlive)
            {
                this.isThreadRunning = false;

                this.thread.Join();
            }
        }

        #endregion
        #region 이미지 구하기 - GetImage()

        /// <summary>
        /// 이미지 구하기
        /// </summary>
        /// <returns>이미지</returns>
        public ImageSource GetImage()
        {
            BitmapSource bitmapSource = this.image.Source as BitmapSource;
            
            return bitmapSource.Clone() as BitmapSource;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Private

        #region 로그 콜백 설정하기 - SetLogCallback()

        /// <summary>
        /// 로그 콜백 설정하기
        /// </summary>
        private static unsafe void SetLogCallback()
        {
            ffmpeg.av_log_set_level(ffmpeg.AV_LOG_VERBOSE);

            av_log_set_callback_callback callback = (p0, level, format, vl) =>
            {
                if(level > ffmpeg.av_log_get_level())
                {
                    return;
                }

                int lineSize = 1024;

                byte* lineBuffer = stackalloc byte[lineSize];

                int printPrefix = 1;

                ffmpeg.av_log_format_line(p0, level, format, vl, lineBuffer, lineSize, &printPrefix);

                string line = Marshal.PtrToStringAnsi((IntPtr)lineBuffer);
            };

            ffmpeg.av_log_set_callback(callback);
        }

        #endregion
        #region 이미지 소스 설정하기 - SetImageSource(bitmap)

        /// <summary>
        /// 이미지 소스 설정하기
        /// </summary>
        /// <param name="bitmap">비트맵</param>
        private void SetImageSource(System.Drawing.Bitmap bitmap)
        {
            this.dispatcher.BeginInvoke((Action)(() =>
            {
                using(MemoryStream memoryStream = new MemoryStream())
                {
                    if(this.thread.IsAlive)
                    {
                        bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Bmp);

                        memoryStream.Position = 0;

                        BitmapImage bitmapimage = new BitmapImage();

                        bitmapimage.BeginInit();

                        bitmapimage.CacheOption  = BitmapCacheOption.OnLoad;
                        bitmapimage.StreamSource = memoryStream;

                        bitmapimage.EndInit();

                        this.image.Source = bitmapimage;
                    }
                }
            }));
        }

        #endregion
        #region 스레드 처리하기 - ProcessThread()

        /// <summary>
        /// 스레드 처리하기
        /// </summary>
        private unsafe void ProcessThread()
        {
            using(VideoStreamDecoder decoder = new VideoStreamDecoder($"video={this.deviceName}"))
            {
                IReadOnlyDictionary<string, string> contextInfoDictionary = decoder.GetContextInfoDictionary();

                contextInfoDictionary.ToList().ForEach(x => Console.WriteLine($"{x.Key} = {x.Value}"));

                Size          sourceSize        = decoder.FrameSize;
                AVPixelFormat sourcePixelFormat = decoder.PixelFormat;
                Size          targetSize        = sourceSize;
                AVPixelFormat targetPixelFormat = AVPixelFormat.AV_PIX_FMT_BGR24;

                using(VideoFrameConverter converter = new VideoFrameConverter(sourceSize, sourcePixelFormat, targetSize, targetPixelFormat))
                {
                    int frameNumber = 0;

                    while(decoder.TryDecodeNextFrame(out AVFrame sourceFrame) && this.isThreadRunning)
                    {
                        AVFrame targetFrame = converter.Convert(sourceFrame);

                        System.Drawing.Bitmap bitmap;

                        bitmap = new System.Drawing.Bitmap
                        (
                            targetFrame.width,
                            targetFrame.height,
                            targetFrame.linesize[0],
                            System.Drawing.Imaging.PixelFormat.Format24bppRgb,
                            (IntPtr)targetFrame.data[0]
                        );

                        SetImageSource(bitmap);

                        frameNumber++;
                    }
                }
            }
        }

        #endregion
    }
}

 

[TestProject 프로젝트]

▶ MainWindow.xaml

<Window x:Class="TestProject.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:library="clr-namespace:TestLibrary;assembly=TestLibrary"
    Width="800"
    Height="600"
    Title="FFMpeg을 사용해 웹 카메라 사용하기 (기능 개선)"
    FontFamily="나눔고딕코딩"
    FontSize="16">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"    />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Border Grid.Row="0"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            BorderThickness="1"
            BorderBrush="Black">
            <library:CameraControl Name="cameraControl"
                Width="600"
                Height="480" />
        </Border>
        <StackPanel Grid.Row="1"
            HorizontalAlignment="Right"
            VerticalAlignment="Center"
            Orientation="Horizontal">
            <Button Name="startButton"
                Width="100"
                Height="30"
                Content="시작" />
            <Button Name="imageButton"
                Margin="10 0 0 0"
                Width="100"
                Height="30"
                Content="이미지" />
        </StackPanel>
    </Grid>
</Window>

 

▶ MainWindow.xaml.cs

using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

using TestLibrary;

namespace TestProject
{
    /// <summary>
    /// 메인 윈도우
    /// </summary>
    public partial class MainWindow : Window
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - MainWindow()

        /// <summary>
        /// 생성자
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            Closing                += Window_Closing;
            this.startButton.Click += startButton_Click;
            this.imageButton.Click += imageButton_Click;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Private
        //////////////////////////////////////////////////////////////////////////////// Event

        #region 윈도우 닫을 경우 처리하기 - Window_Closing(sender, e)

        /// <summary>
        /// 윈도우 닫을 경우 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void Window_Closing(object sender, CancelEventArgs e)
        {
            this.cameraControl.Stop();
        }

        #endregion
        #region 시작 버튼 클릭시 처리하기 - startButton_Click(sender, e)

        /// <summary>
        /// 시작 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void startButton_Click(object sender, RoutedEventArgs e)
        {
            if(this.startButton.Content.ToString() == "시작")
            {
                this.cameraControl.DeviceName = CameraControl.GetDeviceNameList().First();

                this.cameraControl.Start();

                this.startButton.Content = "중단";
            }
            else
            {
                this.cameraControl.Stop();

                this.startButton.Content = "시작";
            }
        }

        #endregion
        #region 이미지 버튼 클릭시 처리하기 - imageButton_Click(sender, e)

        /// <summary>
        /// 이미지 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void imageButton_Click(object sender, RoutedEventArgs e)
        {
            ImageSource imageSource = this.cameraControl.GetImage();

            PngBitmapEncoder encoder = new PngBitmapEncoder();

            encoder.Frames.Add(BitmapFrame.Create(imageSource as BitmapImage));

            using(FileStream stream = new FileStream("d:\\sample.png", FileMode.Create, FileAccess.Write, FileShare.None))
            {
                encoder.Save(stream);
            }
        }

        #endregion
    }
}
728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요