성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] 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...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
글쓰기
제목
이름
암호
전자우편
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(FFmpeg.AutoGen): Bitmap으로부터 yuv420p + rawvideo 형식의 파일로 쓰기</h1> <p> 자, 이제 좀 ffmpeg를 뚝딱거리다 보니 뭔가 보이는 듯합니다. ^^<br /> <br /> 이번에는 예전에 Bitmap 파일을 읽어 동영상 파일을 만든 것처럼,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - OpenCvSharp.VideoWriter에 BMP 파일을 1초씩 출력하는 예제 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12485'>https://www.sysnet.pe.kr/2/0/12485</a> </pre> <br /> ffmpeg를 이용해 PNG 파일을 Bitmap 클래스로 읽어들여 YUV420P 포맷으로 변경 후, 지난 글에서 알게 된,<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(FFmpeg.AutoGen)를 이용한 비디오 프레임의 크기 및 포맷 변경 예제(scaling_video.c) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12961'>https://www.sysnet.pe.kr/2/0/12961</a> </pre> <br /> rawvideo 형식으로 출력해 보겠습니다. 이를 위해 우선 PNG 파일을 bitmap으로 읽고 BitmapData를 이용해 BGRA 순으로 쌓인 RGB 데이터를 가져옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > string bmpFilePath = @"D:\media_sample\1922x1082_sample.png"; Image img = Bitmap.FromFile(bmpFilePath); Bitmap bmp = new Bitmap(img); int fps = 30; int seconds = 5; Rectangle imgSize = new Rectangle(0, 0, img.Width, img.Height); <span style='color: blue; font-weight: bold'>BitmapData bitmapData = bmp.LockBits(imgSize, ImageLockMode.ReadOnly, bmp.PixelFormat);</span> Console.WriteLine(img.PixelFormat); // Format32bppArgb AVPixelFormat srcFormat = (img.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb) ? AVPixelFormat.AV_PIX_FMT_BGRA : AVPixelFormat.AV_PIX_FMT_BGR24; </pre> <br /> 그다음, sws_scale을 이용해 이 포맷을 YUV420P로 변환하면 됩니다. 이때, <a target='tab' href='https://www.sysnet.pe.kr/2/0/12958'>sws_scale에는 원본 및 타깃 이미지와 그것의 stride 정보</a>를 전달해야 하는데요,<br /> <br /> 우선, 원본 이미지의 data와 linesize는 BitmapData로부터 구할 수 있으므로 다음과 같이 초기화할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > byte_ptrArray4 src_data = new byte_ptrArray4(); int_array4 src_linesize = new int_array4(); <span style='color: blue; font-weight: bold'>src_data[0] = (byte *)bitmapData.Scan0.ToPointer();</span> // BGRA 포맷은 단일 data[0]만 사용 <span style='color: blue; font-weight: bold'>src_linesize[0] = bitmapData.Stride;</span> // data[0]의 이미지에서 한 라인에 대한 색상 정보를 담고 있는 바이트 크기 </pre> <br /> 포맷 변환을 해서 YUV 데이터를 담을 데이터도 위와 같은 식으로 초기화할 수 있는데요, 하지만 실수할 수 있으므로 직접 초기화하기보다는 ffmpeg 라이브러리에서 제공하는 함수를 사용해 공간을 할당받는 것도 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > byte_ptrArray4 dst_data = new byte_ptrArray4(); int_array4 dst_linesize = new int_array4(); AVPixelFormat dst_pix_fmt = AVPixelFormat.AV_PIX_FMT_YUV420P; if ((ret = <span style='color: blue; font-weight: bold'>ffmpeg.av_image_alloc(ref dst_data, ref dst_linesize, img.Width, img.Height, dst_pix_fmt, 1)</span>) < 0) { Console.WriteLine("Could not allocate destination image"); break; } int dst_bufsize = ret; // == img.Width * img.Height * 3 / 2, YUV420P 12bpp </pre> <br /> 위와 같이 av_image_alloc에 AV_PIX_FMT_YUV420P 포맷으로 Width, Height에 해당하는 정보를 전달하면 알아서 dst_data[0], dst_data[1], dst_data[2]에 메모리 할당을 하고, 그것의 dst_linesize[0], dst_linesize[1], dst_linesize[2]도 값을 채워서 반환해줍니다.<br /> <br /> 자, 그럼 준비가 되었군요. 이제 sws_scale을 이용해 다음과 같이 RGB에서 YUV로 변환을 할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > SwsContext* sws_ctx = ffmpeg.sws_getContext(img.Width, img.Height, <span style='color: blue; font-weight: bold'>srcFormat</span>, img.Width, img.Height, <span style='color: blue; font-weight: bold'>dst_pix_fmt</span>, ffmpeg.SWS_BILINEAR, null, null, null); <span style='color: blue; font-weight: bold'>ffmpeg.sws_scale(sws_ctx, src_data, src_linesize, 0, img.Height, dst_data, dst_linesize);</span> </pre> <br /> 그럼, dst_data에는 변환된 YUV420P 이미지 데이터가 쌓이고, 이 버퍼의 크기를 그대로 파일에 쓰면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ReadOnlySpan<byte> buffer = new ReadOnlySpan<byte>(dst_data[0], dst_bufsize); fs.Write(buffer); </pre> <br /> ffplay가 재생할 수 있는 rawvideo 형식의 파일이 생성됩니다.<br /> <br /> 참고로, rawvideo의 경우 딱히 출력 파일에 fps를 기록할 수 있는 헤더 데이터가 없습니다. 하지만, ffplay의 fps 기본값이 25이기 때문에 다음과 같은 식으로 25fps씩 5번 파일로 저장하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int fps = 25; int seconds = 5; for (int i = 0; i < fps * seconds; i++) { ret = ffmpeg.sws_scale(sws_ctx, src_data, src_linesize, 0, img.Height, dst_data, dst_linesize); if (ret < 0) { Console.WriteLine("sws_scale failed"); break; } ReadOnlySpan<byte> buffer = new ReadOnlySpan<byte>(dst_data[0], dst_bufsize); fs.Write(buffer); } </pre> <br /> 5초 분량의 재생 시간을 갖는 rawvideo 동영상이 만들어집니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그나저나 align 값에 대해 알아볼까요? ^^ 가령, 아래의 코드에서 1로 주고 있는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > if ((ret = ffmpeg.av_image_alloc(ref dst_data, ref dst_linesize, img.Width, img.Height, dst_pix_fmt, <span style='color: blue; font-weight: bold'>1</span>)) < 0) { Console.WriteLine("Could not allocate destination image"); break; } </pre> <br /> 그럼 모든 이미지에 대해 dst_linesize[0] == (이미지의 width) 값이 나옵니다. 하지만 만약 1922x1082 이미지에 대해 align 값을 바꿔보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > align == 1 dst_linesize[0] 1922 dst_linesize[1] 961 dst_linesize[2] 961 align == 8 dst_linesize[0] 1928 dst_linesize[1] 968 dst_linesize[2] 968 align == 16 dst_linesize[0] 1936 dst_linesize[1] 976 dst_linesize[2] 976 align == 32 dst_linesize[0] 1952 dst_linesize[1] 992 dst_linesize[2] 992 </pre> <br /> 이렇게 값이 나옵니다. 즉, 1922 크기가 8, 16, 32에 대해 정확히 나눠떨어지지 않기 때문에 1922 크기가 나오지 않는 것입니다. 그런데 재미있는 것은, 저렇게 한 경우 sws_scale 함수가 실패하지는 않습니다. 대신 출력된 최종 파일을 ffplay로 재생하면 이미지가 모두 깨진 채로 나오는 정도의 차이만 있습니다. <br /> <br /> 그러니까, 속도를 높이기 위해 32나 64 정도로 나눠떨어지는지 테스트를 하고는, 그렇지 않은 경우 그냥 1을 주면 됩니다.<br /> <br /> 실제로 Width == 1922와 같은 이미지인 경우 sws_scale 함수를 실행하면 다음과 같은 경고가 떨어집니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [swscaler @ 000002533A660000] Warning: data is not aligned! This can lead to a speed loss </pre> <br /> 인코딩 속도가 떨어질 수 있다는 것 같은데 어쩔 수 없습니다. 입력 이미지를 1920과 같은 잘 정렬된 크기의 것으로 맞춰주는 것이 좋습니다.<br /> <br /> 어쨌든, 예제를 실행해 생성된 dat 파일을 다음과 같은 식의 ffplay로 열면 PNG 이미지가 5초 동안 재생되는 것을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > D:\media_sample> <span style='color: blue; font-weight: bold'>ffplay -framerate 25 -autoexit -f rawvideo -pixel_format yuv420p -video_size 1922x1082 c:\temp\output\bmp2yuv420p.dat</span> ...[생략]... [rawvideo @ 00000235C4F7C400] Estimating duration from bitrate, this may be inaccurate Input #0, rawvideo, from 'c:\temp\output\bmp2yuv420p.dat': <span style='color: blue; font-weight: bold'>Duration: 00:00:05.00</span>, start: 0.000000, bitrate: 623881 kb/s Stream #0:0: Video: rawvideo (I420 / 0x30323449), yuv420p, 1922x1082, 623881 kb/s, 25 tbr, 25 tbn, 25 tbc 4.94 M-V: 0.001 fd= 0 aq= 0KB vq= 0KB sq= 0B f=0/0 </pre> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1903&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1972
(왼쪽의 숫자를 입력해야 합니다.)