성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
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)를 이용한 http_multiclient.c 예제 포팅</h1> <p> 지난 글에 이어,<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)를 이용한 transcode_aac.c 예제 포팅 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12991'>https://www.sysnet.pe.kr/2/0/12991</a> </pre> <br /> 이번에는 <a target='tab' href='https://ffmpeg.org/doxygen/trunk/examples.html'>ffmpeg 예제</a> 중 "<a target='tab' href='https://ffmpeg.org/doxygen/trunk/http_multiclient_8c-example.html'>http_multiclient.c</a>" 파일을 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;' > using FFmpeg.AutoGen; using FFmpeg.AutoGen.Example; using System; using System.IO; using System.Runtime.InteropServices; using System.Threading; namespace http_multiclient { internal unsafe class Program { static void process_client(AVIOContext* client, string in_uri) { AVIOContext* input = null; byte* buf = stackalloc byte[1024]; Span<byte> buffer = new(buf, 1024); int ret, n, reply_code; byte* resource = null; string? text = null; while ((ret = ffmpeg.avio_handshake(client)) > 0) { ffmpeg.av_opt_get(client, "resource", ffmpeg.AV_OPT_SEARCH_CHILDREN, &resource); text = Marshal.PtrToStringAnsi(new IntPtr(resource)); if (string.IsNullOrEmpty(text) == false) { break; } ffmpeg.av_freep(&resource); } if (ret < 0) { goto end; } ffmpeg.av_log(client, ffmpeg.AV_LOG_TRACE, $"resource=0x{new IntPtr(resource):x}\n"); if (text != null && text[0] == '/' /* && text.Substring(1) == in_uri */) { reply_code = 200; } else { reply_code = ffmpeg.AVERROR_HTTP_NOT_FOUND; } if ((ret = ffmpeg.av_opt_set_int(client, "reply_code", reply_code, ffmpeg.AV_OPT_SEARCH_CHILDREN)) < 0) { ffmpeg.av_log(client, ffmpeg.AV_LOG_ERROR, $"Failed to set reply_code: {FFmpegHelper.av_err2str(ret)}\n"); goto end; } ffmpeg.av_log(client, ffmpeg.AV_LOG_TRACE, $"Set reply code to {reply_code}\n"); while ((ret = ffmpeg.avio_handshake(client)) > 0) ; if (ret < 0) { goto end; } Console.WriteLine("Handshake performed"); if (reply_code != 200) { goto end; } Console.WriteLine("Opening input file"); if ((ret = ffmpeg.avio_open2(&input, in_uri, ffmpeg.AVIO_FLAG_READ, null, null)) < 0) { ffmpeg.av_log(input, ffmpeg.AV_LOG_ERROR, $"Failed to open input: {in_uri}: {FFmpegHelper.av_err2str(ret)}\n"); goto end; } for (; ; ) { n = ffmpeg.avio_read(input, buf, buffer.Length); if (n < 0) { if (n == ffmpeg.AVERROR_EOF) { break; } ffmpeg.av_log(input, ffmpeg.AV_LOG_ERROR, $"Error reading from input: {FFmpegHelper.av_err2str(n)}\n"); break; } ffmpeg.avio_write(client, buf, n); ffmpeg.avio_flush(client); } end: Console.WriteLine("Flushing client"); ffmpeg.avio_flush(client); Console.WriteLine("Closing clinet"); ffmpeg.avio_close(client); Console.WriteLine("Closing input"); ffmpeg.avio_close(input); ffmpeg.av_freep(&resource); } static unsafe int Main(string[] args) { FFmpegBinariesHelper.RegisterFFmpegBinaries(); #if DEBUG Console.WriteLine("Current directory: " + Environment.CurrentDirectory); Console.WriteLine("Running in {0}-bit mode.", Environment.Is64BitProcess ? "64" : "32"); Console.WriteLine($"FFmpeg version info: {ffmpeg.av_version_info()}"); Console.WriteLine(); #endif AVDictionary* options = null; AVIOContext* client = null; AVIOContext* server = null; string in_uri, out_uri; int ret; ffmpeg.av_log_set_level(ffmpeg.AV_LOG_TRACE); out_uri = "http://127.0.0.1:15384/"; string dirPath = Path.GetDirectoryName(typeof(Program).Assembly.Location) ?? ""; in_uri = Path.Combine(dirPath, "..", "..", "..", "Samples", "mpeg1video_q0.m1v"); ffmpeg.avformat_network_init(); if ((ret = ffmpeg.av_dict_set(&options, "listen", "2", 0)) < 0) { Console.WriteLine($"Failed to set listen mode for server: {FFmpegHelper.av_err2str(ret)}"); return ret; } if ((ret = ffmpeg.avio_open2(&server, out_uri, ffmpeg.AVIO_FLAG_WRITE, null, &options)) < 0) { Console.WriteLine($"Failed to open server: {FFmpegHelper.av_err2str(ret)}"); return ret; } Console.WriteLine("Entering main loop"); for (; ;) { if ((ret = ffmpeg.avio_accept(server, &client)) < 0) { goto end; } Console.WriteLine("Accepted client, forking/thraeding process."); AVIOContext** pClient = &client; Thread t = new Thread((obj) => { Console.WriteLine("Client...."); process_client(*pClient, in_uri); }); t.Start(); } end: ffmpeg.avio_close(server); if (ret < 0 && ret != ffmpeg.AVERROR_EOF) { Console.WriteLine($"Some errors occurred: {FFmpegHelper.av_err2str(ret)}"); return 1; } return 0; } } } </pre> <br /> 원본 "<a target='tab' href='https://ffmpeg.org/doxygen/trunk/http_multiclient_8c-example.html'>http_multiclient.c</a>" 코드는 리눅스의 fork를 이용하지만, 위의 코드는 스레드를 이용했다는 정도의 차이가 있습니다.<br /> <br /> 잘 동작하는지 테스트까지 해볼까요? ^^<br /> <br /> 이 코드는, 예제 소개에서처럼 "This example will serve a file without decoding or demuxing it over http.Multiple clients can connect and will receive the same file."이라고, 동영상 파일에 대해 어떠한 디코딩도, 디먹싱도 하지 않습니다.<br /> <br /> 그렇다면, 클라이언트 측에서 어차피 디먹싱, 디코딩을 할 것이므로 온전한 mp4 파일을 서비스하면 될 것 같은데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // <a target='tab' href='https://github.com/stjeong/ffmpeg_autogen_cs/tree/master/Samples'>repo에 포함한 예제 비디오 파일</a> in_uri = Path.Combine(dirPath, "..", "..", "..", "Samples", "sample-10s.mp4"); </pre> <br /> 그런데, 이것을 GOM 플레이어로 열었더니 재생시간은 10초 정도로 정확히 나오는데 영상은 그냥 검은 화면으로 재생이 됩니다.<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(FFmpeg.AutoGen)를 이용한 비디오 디코딩 예제(decode_video.c) - 세 번째 이야기 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12960'>https://www.sysnet.pe.kr/2/0/12960</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // <a target='tab' href='https://github.com/stjeong/ffmpeg_autogen_cs/tree/master/Samples'>repo에 포함한 예제 비디오 파일</a>: 컨테이너 없이 비디오 영상만 포함 in_uri = Path.Combine(dirPath, "..", "..", "..", "Samples", "mpeg1video_q0.m1v"); </pre> <br /> 서비스했더니, 이번에는 GOM 플레이어에서 해당 동영상 재생이 잘 됩니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='http_multi_clnt_1.png' src='/SysWebRes/bbs/http_multi_clnt_1.png' /><br /> <br /> 또한 ffplay로도 다음과 같은 옵션으로 재생이 잘 되고.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ffplay -autoexit http://127.0.0.1:15384/ ffplay -autoexit -f mpegvideo http://127.0.0.1:15384/ </pre> <br /> 다른 입력 파일도 해볼까요? 이전에 hw_decode 포팅에서,<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)로 하드웨어 가속기를 이용한 비디오 디코딩 예제(hw_decode.c) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12932'>https://www.sysnet.pe.kr/2/0/12932</a> </pre> <br /> 출력한 파일은 컨테이너도 없고, raw 형식으로 NV12 포맷으로 디코딩된 데이터를 포함하고 있습니다. 이 파일로 서비스하게 되면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // <a target='tab' href='https://www.sysnet.pe.kr/2/0/12932'>hw_decode 예제</a>를 실행해 얻은 파일 in_uri = @"C:\ffmpeg_autogen_cs\hw_decode\bin\Debug\test.dat"; </pre> <br /> GOM 플레이어로는 이제 재생할 수 없고, ffplay에서는 다음과 같은 옵션으로 재생할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // test.dat 파일이 1920x1080 동영상이고 nv12 raw 포맷을 가지므로. ffplay -autoexit -f rawvideo -pixel_format nv12 -video_size 1920x1080 http://127.0.0.1:15384/ </pre> <br /> (<a target='tab' href='https://github.com/stjeong/ffmpeg_autogen_cs/tree/master/http_multiclient'>이 글의 소스 코드는 github에 올려</a>져 있습니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그런데, 재생 시간이 좀 흥미롭습니다.<br /> <br /> 테스트를 위해 (<a target='tab' href='https://github.com/stjeong/ffmpeg_autogen_cs/tree/master/Samples'>repo의 Samples 디렉터리</a>에 포함시킨 5.00초 분량의) file_example_MP4_1920_18MG.mp4 파일을 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12932'>hw_decode 예제</a>로 출력한 test.dat 파일을 ffplay로 fps를 지정해 재생해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ffplay -autoexit -f rawvideo -framerate 30 -pixel_format nv12 -video_size 1920x1080 test.dat </pre> <br /> 4.96 ~ 4.98초 정도의 재생 시간이 나옵니다. 이것을 http_multiclient 예제를 통해 서비스하는 것을 재생해보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ffplay -autoexit -f rawvideo -framerate 30 -pixel_format nv12 -video_size 1920x1080 http://127.0.0.1:15384/ </pre> <br /> 대략 7.77 ~ 8.10초 정도의 재생 시간이 나옵니다. 게다가, http_multiclient를 이용해 재생하는 경우에는 -framerate 옵션의 지정이 별다른 효력이 없었습니다. 즉, -framerate를 70으로 지정해도 동일하게 약 7.8초 정도의 재생 시간이 나왔습니다.<br /> <br /> 반면, (컨테이너는 없지만) 인코딩만은 시켰던 "mpeg1video_q0.m1v" 파일로 테스트하면 원래의 5.00초 정도로 재생이 잘 됩니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1510
(왼쪽의 숫자를 입력해야 합니다.)