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
그리드형(광고전용)
'C# > WPF' 카테고리의 다른 글
[C#/WPF] Grid 클래스 : 셀 패딩 속성 추가하기 (0) | 2019.07.13 |
---|---|
[C#/WPF] 움직이는 랩 패널(Wrap Panel) 사용하기 (0) | 2019.07.12 |
[C#/WPF] 3D 큐브 애니메이션 만들기 (0) | 2019.07.06 |
[C#/WPF] DoubleAnimation 클래스 : 동심원 애니메이션 만들기 (0) | 2019.07.05 |
[C#/WPF] FFMpeg을 사용해 RTP/RTSP 영상 재생하기 (0) | 2019.07.04 |
[C#/WPF] 별(star) 그리기 (0) | 2019.07.02 |
[C#/WPF] 큐브 변형하기 (0) | 2019.07.01 |
[C#/WPF] Viewport3D 클래스 사용하기 (0) | 2019.06.30 |
[C#/WPF] DiffuseMaterial 클래스 사용하기 (0) | 2019.06.30 |
[C#/WPF] Model3DGroup 클래스 사용하기 (0) | 2019.06.30 |