첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
------------------------------------------------------------------------------------------------------------------------------------------------------
728x90
728x170

TestProject.z01
다운로드
TestProject.z02
다운로드
TestProject.zip
다운로드

▶ FFMpegHelper.cs

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

using FFmpeg.AutoGen;

namespace TestProject
{
    /// <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, "FFMpegDLL");

                        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
    }
}

 

728x90

 

▶ VideoFrameConverter.cs

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

using FFmpeg.AutoGen;

namespace TestProject
{
    /// <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
    }
}

 

300x250

 

▶ VideoStreamDecoder.cs

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

using FFmpeg.AutoGen;

namespace TestProject
{
    /// <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
    }
}

 

▶ 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"
    Width="800"
    Height="600"
    Title="FFMpeg을 사용해 웹 카메라 사용하기"
    FontFamily="나눔고딕코딩"
    FontSize="16">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"    />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Border Grid.Row="0"
            Margin="10"
            BorderThickness="1"
            BorderBrush="Black">
            <Image Name="image"
                HorizontalAlignment="Stretch"
                VerticalAlignment="Stretch" />
        </Border>
        <Button Name="playButton" Grid.Row="1"
            Margin="0 0 10 10"
            HorizontalAlignment="Right"
            Width="100"
            Height="30"
            Content="재생" />
    </Grid>
</Window>

 

▶ MainWindow.xaml.cs

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

using FFmpeg.AutoGen;

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

        #region Field

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

        /// <summary>
        /// 장치명
        /// </summary>
        private string deviceName = "USB2.0 PC CAMERA";

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

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

        #endregion

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

        #region 생성자 - MainWindow()

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

            FFMpegHelper.Register();

            Closing               += Window_Closing;
            this.playButton.Click += playButton_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)
        {
            if(this.thread.IsAlive)
            {
                this.isThreadRunning = false;

                this.thread.Join();
            }
        }

        #endregion
        #region 재생 버튼 클릭시 처리하기 - playButton_Click(sender, e)

        /// <summary>
        /// 재생 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void playButton_Click(object sender, RoutedEventArgs e)
        {
            if(this.isThreadRunning)
            {
                this.isThreadRunning = false;
            }
            else
            {
                this.isThreadRunning = true;

                this.thread = new Thread(ProcessThread);

                this.thread.Start();
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Function

        #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) && 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
    }
}
728x90
그리드형(광고전용)
Posted by icodebroker
,