성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - FFmpeg.AutoGen 라이브러리를 이용한 기본 프로젝트 구성</h1> 이와 관련한 동영상 강의를 마침 2022년 5월 6일에 진행된 ".NET Conf Mini"에서 했으니 참고하세요. ^^ <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 개발자를 위한 ffmpeg 라이브러리 사용법 ; <a target='tab' href='https://youtu.be/ERU9tXdtHLg'>https://youtu.be/ERU9tXdtHLg</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> <p> 원래의 네이티브 ffmpeg 라이브러리를 단순히 API만 PInvoke 수준에 가깝게 래핑해서 제공하는 라이브러리가 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Ruslan-B/FFmpeg.AutoGen ; <a target='tab' href='https://github.com/Ruslan-B/FFmpeg.AutoGen'>https://github.com/Ruslan-B/FFmpeg.AutoGen</a> </pre> <br /> 그렇다면 당연히 C# 라이브러리와 함께 네이티브 모듈들이 있어야 정상 동작할 텐데요, 윈도우용에 한해서는 친절하게 x64 DLL들을 빌드시켜 github repo에 포함하고 있으니 그것을 사용하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > FFmpeg.AutoGen/FFmpeg/bin/ ; <a target='tab' href='https://github.com/Ruslan-B/FFmpeg.AutoGen/tree/master/FFmpeg/bin/x64'>https://github.com/Ruslan-B/FFmpeg.AutoGen/tree/master/FFmpeg/bin/x64</a> </pre> <br /> 혹은, vcpkg가 있으니,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 오픈 소스 라이브러리를 쉽게 빌드해 주는 "C++ Package Manager for Windows: vcpkg" ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/11409'>https://www.sysnet.pe.kr/2/0/11409</a> </pre> <br /> 다음의 명령으로 빌드해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > c:\vcpkg> <span style='color: blue; font-weight: bold'>vcpkg install ffmpeg:x64-windows</span> </pre> <br /> .\vcpkg\packages\ffmpeg_x64-windows\bin 디렉터리에 빌드 완료된 DLL들을 사용해도 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > avcodec-58.dll avdevice-58.dll avfilter-7.dll avformat-58.dll avutil-56.dll swresample-3.dll swscale-5.dll </pre> <br /> 이를 기반으로 FFmpeg.AutoGen을 사용하기 위해서는 "<a target='tab' href='https://github.com/Ruslan-B/FFmpeg.AutoGen/tree/master/FFmpeg.AutoGen.Example'>https://github.com/Ruslan-B/FFmpeg.AutoGen/tree/master/FFmpeg.AutoGen.Example</a>" 예제 프로젝트의 소스 코드를 참고하면 됩니다.<br /> <br /> 그럼, 한번 구성해 볼까요? ^^<br /> <br /> 우선, 콘솔 프로젝트를 생성하고 FFmpeg.AutoGen 참조를 추가합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Install-Package FFmpeg.AutoGen -Version 4.4.1.1 // Install-Package FFmpeg.AutoGen -Version 5.0.0 Install-Package FFmpeg.AutoGen </pre> <br /> 그다음, "<a target='tab' href='https://github.com/Ruslan-B/FFmpeg.AutoGen/tree/master/FFmpeg.AutoGen.Example'>예제 프로젝트</a>"에서 FFmpegHelper.cs, FFmpegBinariesHelper.cs 2개의 파일을 복사해 콘솔 프로젝트에 "FFmpeg" 디렉터리를 만들어 추가한 후, Program.cs에 FFmpegBinariesHelper.cs의 메서드를 하나 호출합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using FFmpeg.AutoGen.Example; using System; namespace ConsoleApp1 { internal class Program { static void Main(string[] args) { Console.WriteLine("Current directory: " + Environment.CurrentDirectory); Console.WriteLine("Running in {0}-bit mode.", Environment.Is64BitProcess ? "64" : "32"); <span style='color: blue; font-weight: bold'>FFmpegBinariesHelper.RegisterFFmpegBinaries();</span> Console.WriteLine($"FFmpeg version info: {ffmpeg.av_version_info()}"); } } } </pre> <br /> 그러니까, RegisterFFmpegBinaries 메서드의 역할은 (위에서 vcpkg로 빌드한) 네이티브 라이브러리를 찾는 경로를 지정합니다. 코드를 한번 볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.IO; using System.Runtime.InteropServices; namespace FFmpeg.AutoGen.Example { public class FFmpegBinariesHelper { internal static void RegisterFFmpegBinaries() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { <span style='color: blue; font-weight: bold'>var current = Environment.CurrentDirectory; var probe = Path.Combine("FFmpeg", "bin", Environment.Is64BitProcess ? "x64" : "x86"); while (current != null) { var ffmpegBinaryPath = Path.Combine(current, probe); if (Directory.Exists(ffmpegBinaryPath)) { Console.WriteLine($"FFmpeg binaries found in: {ffmpegBinaryPath}"); ffmpeg.RootPath = ffmpegBinaryPath; return; } current = Directory.GetParent(current)?.FullName;</span> } } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { ffmpeg.RootPath = "/lib/x86_64-linux-gnu/"; } else { throw new NotSupportedException(); // fell free add support for platform of your choose } } } } </pre> <br /> 보는 바와 같이 윈도우를 대상으로 한다면, 실행 파일 위치를 기준으로 ./FFmpeg/bin/ 하위의 플랫폼에 따른 x64/x86 디렉터리에 ffmpeg 네이티브 라이브러리가 있다는 것을 가정하고 있습니다.<br /> <br /> 물론, 저 경로를 바꿔도 되겠지만 그냥 맞춰줘도 무방합니다. 혹은, 아예 저 코드를 통째로 날리고 다음과 같이 한 줄로 자신이 포함한 네이티브 라이브러리의 위치를 지정하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ffmpeg.RootPath = Path.Combine(Environment.CurrentDirectory, "FFmpeg", "bin", "x64"); </pre> <br /> 자, 그럼 위의 경로를 맞춰주기 위해 vcpkg로 빌드한(또는, github repo에서 다운로드한) DLL들을 콘솔 프로젝트의 "FFmpeg/bin/x64" 디렉터리에 복사합니다. 여기까지의 솔루션 구성은 다음과 같습니다.<br /> <br /> <img alt='ffmpeg_prj_1.png' src='/SysWebRes/bbs/ffmpeg_prj_1.png' /><br /> <br /> 당연히 이렇게만 하면 안 되죠? ^^ 빌드 시에 위에서 포함한 DLL들이 복사가 되도록 각각의 DLL 파일에 대한 "Copy to Output Directory" 값을 "Copy if newer"로 바꿔줍니다. (그리고 잊지 말아야 할 것은, 위와 같이 x64용 DLL을 사용하는 경우라면 콘솔 프로젝트의 속성 창에서 "<a target='tab' href='https://www.sysnet.pe.kr/2/0/11909#prefer32bit'>Prefer 32-bit</a>" 옵션을 꺼야 합니다.)<br /> <br /> 여기까지가 준비 과정의 끝입니다. 이후의 작업은 ffmpeg 라이브러리를 사용한 경험이 있다면 그에 따라 FFmpeg.AutoGen의 "ffempg" 정적 속성에서 제공하는 래퍼 API를 호출하는 식으로 작업을 진행하면 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <a name='list_hw_decoder'></a> <br /> 기왕에 하는 거, 그럼 기본 예제 프로젝트만이라도 포팅을 해볼까요? ^^ 해당 프로젝트에서 로그 설정과 Hardware Decoder를 조회하는 코드를 복사해 옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { Console.WriteLine("Current directory: " + Environment.CurrentDirectory); Console.WriteLine("Running in {0}-bit mode.", Environment.Is64BitProcess ? "64" : "32"); FFmpegBinariesHelper.RegisterFFmpegBinaries(); Console.WriteLine($"FFmpeg version info: {ffmpeg.av_version_info()}"); <span style='color: blue; font-weight: bold'>SetupLogging(); ConfigureHWDecoder(out var deviceType);</span> } private static void ConfigureHWDecoder(out AVHWDeviceType HWtype) { HWtype = AVHWDeviceType.AV_HWDEVICE_TYPE_NONE; Console.WriteLine("Use hardware acceleration for decoding?[n]"); var key = Console.ReadLine(); var availableHWDecoders = new Dictionary<int, AVHWDeviceType>(); if (key == "y") { Console.WriteLine("Select hardware decoder:"); var type = AVHWDeviceType.AV_HWDEVICE_TYPE_NONE; var number = 0; while ((type = ffmpeg.av_hwdevice_iterate_types(type)) != AVHWDeviceType.AV_HWDEVICE_TYPE_NONE) { Console.WriteLine($"{++number}. {type}"); availableHWDecoders.Add(number, type); } if (availableHWDecoders.Count == 0) { Console.WriteLine("Your system have no hardware decoders."); HWtype = AVHWDeviceType.AV_HWDEVICE_TYPE_NONE; return; } var decoderNumber = availableHWDecoders .SingleOrDefault(t => t.Value == AVHWDeviceType.AV_HWDEVICE_TYPE_DXVA2).Key; if (decoderNumber == 0) decoderNumber = availableHWDecoders.First().Key; Console.WriteLine($"Selected [{decoderNumber}]"); int.TryParse(Console.ReadLine(), out var inputDecoderNumber); availableHWDecoders.TryGetValue(inputDecoderNumber == 0 ? decoderNumber : inputDecoderNumber, out HWtype); } } private static unsafe void SetupLogging() { ffmpeg.av_log_set_level(ffmpeg.AV_LOG_VERBOSE); // do not convert to local function av_log_set_callback_callback logCallback = (p0, level, format, vl) => { if (level > ffmpeg.av_log_get_level()) return; var lineSize = 1024; var lineBuffer = stackalloc byte[lineSize]; var printPrefix = 1; ffmpeg.av_log_format_line(p0, level, format, vl, lineBuffer, lineSize, &printPrefix); var line = Marshal.PtrToStringAnsi((IntPtr)lineBuffer); Console.ForegroundColor = ConsoleColor.Yellow; Console.Write(line); Console.ResetColor(); }; ffmpeg.av_log_set_callback(logCallback); } </pre> <br /> 제 컴퓨터에서 실행해 보면, "AV_HWDEVICE_TYPE_DXVA2", "AV_HWDEVICE_TYPE_D3D11VA" 2개의 decoder가 탐색되었는데요, 소스 코드를 보면 Console.ReadLine으로 묻는 과정이 있으므로 이를 생략해 그냥 적절하게 옵션 처리하거나 대중적으로 보이는 AV_HWDEVICE_TYPE_DXVA2 옵션을 기본값으로 시작하는 정도로 코드를 살짝 바꿔 응용하면 되겠습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> Hardware Decoder 유형이 선택되었으면, 이것으로 당연히 프레임을 디코딩할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Console.WriteLine("Decoding..."); DecodeAllFramesToImages(deviceType); private static unsafe void DecodeAllFramesToImages(AVHWDeviceType HWDevice) { // decode all frames from url, please not it might local resorce, e.g. string url = "../../sample_mpeg4.mp4"; var url = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"; // be advised this file holds 1440 frames using var vsd = new VideoStreamDecoder(url, HWDevice); Console.WriteLine($"codec name: {vsd.CodecName}"); var info = vsd.GetContextInfo(); info.ToList().ForEach(x => Console.WriteLine($"{x.Key} = {x.Value}")); var sourceSize = vsd.FrameSize; var sourcePixelFormat = HWDevice == AVHWDeviceType.AV_HWDEVICE_TYPE_NONE ? vsd.PixelFormat : GetHWPixelFormat(HWDevice); var destinationSize = sourceSize; var destinationPixelFormat = AVPixelFormat.AV_PIX_FMT_BGR24; using var vfc = new VideoFrameConverter(sourceSize, sourcePixelFormat, destinationSize, destinationPixelFormat); var frameNumber = 0; while (vsd.TryDecodeNextFrame(out var frame)) { var convertedFrame = vfc.Convert(frame); using (var bitmap = new Bitmap(convertedFrame.width, convertedFrame.height, convertedFrame.linesize[0], PixelFormat.Format24bppRgb, (IntPtr) convertedFrame.data[0])) bitmap.Save($"frame.{frameNumber:D8}.jpg", ImageFormat.Jpeg); Console.WriteLine($"frame: {frameNumber}"); frameNumber++; } } </pre> <br /> 위에 사용한 VideoStreamDecoder는 예제 프로젝트에 포함돼 있으니 그걸 그대로 추가하면 됩니다. 그러니까, 결국 위의 소스 코드는 mp4 파일을 프레임 별로 Bitmap으로 변환 후 JPG 파일로 저장하는 역할을 합니다.<br /> <br /> 그리고 그다음 나오는 EncodeImagesToH264 메서드는, 위에서 저장했던 JPG 파일을 다시 "out.h264" 파일로 인코딩시킵니다. (해당 파일은 곰 플레이어나, <a target='tab' href='https://www.videolan.org/vlc/index.html'>VLC</a>를 이용해 재생할 수 있습니다.) 오호~~~ 간단한 예제 프로젝트 하나에서 동영상 디코딩/인코딩 과정을 모두 다루고 있었군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Console.WriteLine("Encoding..."); EncodeImagesToH264(); private static unsafe void EncodeImagesToH264() { var frameFiles = Directory.GetFiles(".", "frame.*.jpg").OrderBy(x => x).ToArray(); var fistFrameImage = Image.FromFile(frameFiles.First()); var outputFileName = "out.h264"; var fps = 25; var sourceSize = fistFrameImage.Size; var sourcePixelFormat = AVPixelFormat.AV_PIX_FMT_BGR24; var destinationSize = sourceSize; var destinationPixelFormat = AVPixelFormat.AV_PIX_FMT_YUV420P; using var vfc = new VideoFrameConverter(sourceSize, sourcePixelFormat, destinationSize, destinationPixelFormat); using var fs = File.Open(outputFileName, FileMode.Create); using var vse = new H264VideoStreamEncoder(fs, fps, destinationSize); var frameNumber = 0; foreach (var frameFile in frameFiles) { byte[] bitmapData; using (var frameImage = Image.FromFile(frameFile)) using (var frameBitmap = frameImage is Bitmap bitmap ? bitmap : new Bitmap(frameImage)) bitmapData = GetBitmapData(frameBitmap); fixed (byte* pBitmapData = bitmapData) { var data = new byte_ptrArray8 { [0] = pBitmapData }; var linesize = new int_array8 { [0] = bitmapData.Length / sourceSize.Height }; var frame = new AVFrame { data = data, linesize = linesize, height = sourceSize.Height }; var convertedFrame = vfc.Convert(frame); convertedFrame.pts = frameNumber * fps; vse.Encode(convertedFrame); } Console.WriteLine($"frame: {frameNumber}"); frameNumber++; } } </pre> <br /> 처음엔 막막했는데, 초보인 저도 예제 프로젝트 하나만으로 대충 ffmpeg를 사용할 수 있을 것만 같은 자신감이 생기는군요. ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1879&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다. 단지, 압축 파일 크기를 줄이기 위해 ./FFmpeg/bin/x64 하위의 DLL은 제거했습니다.)<br /> </p><br /> <br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1941
(왼쪽의 숫자를 입력해야 합니다.)