Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 24개 있습니다.)
.NET Framework: 1129. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 인코딩 예제(encode_video.c)
; https://www.sysnet.pe.kr/2/0/12898

.NET Framework: 1134. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 디코딩 예제(decode_video.c)
; https://www.sysnet.pe.kr/2/0/12924

.NET Framework: 1135. C# - ffmpeg(FFmpeg.AutoGen)로 하드웨어 가속기를 이용한 비디오 디코딩 예제(hw_decode.c)
; https://www.sysnet.pe.kr/2/0/12932

.NET Framework: 1136. C# - ffmpeg(FFmpeg.AutoGen)를 이용해 MP2 오디오 파일 디코딩 예제(decode_audio.c)
; https://www.sysnet.pe.kr/2/0/12933

.NET Framework: 1137. ffmpeg의 파일 해시 예제(ffhash.c)를 C#으로 포팅
; https://www.sysnet.pe.kr/2/0/12935

.NET Framework: 1138. C# - ffmpeg(FFmpeg.AutoGen)를 이용해 멀티미디어 파일의 메타데이터를 보여주는 예제(metadata.c)
; https://www.sysnet.pe.kr/2/0/12936

.NET Framework: 1139. C# - ffmpeg(FFmpeg.AutoGen)를 이용해 오디오(mp2) 인코딩하는 예제(encode_audio.c)
; https://www.sysnet.pe.kr/2/0/12937

.NET Framework: 1147. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 오디오 필터링 예제(filtering_audio.c)
; https://www.sysnet.pe.kr/2/0/12951

.NET Framework: 1148. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 오디오 필터 예제(filter_audio.c)
; https://www.sysnet.pe.kr/2/0/12952

.NET Framework: 1150. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 디코딩 예제(decode_video.c) - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/12959

개발 환경 구성: 637. ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 디코딩 예제(decode_video.c) - 세 번째 이야기
; https://www.sysnet.pe.kr/2/0/12960

.NET Framework: 1151. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 프레임의 크기 및 포맷 변경 예제(scaling_video.c)
; https://www.sysnet.pe.kr/2/0/12961

.NET Framework: 1153. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 avio_reading.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12964

.NET Framework: 1157. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 muxing.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12971

.NET Framework: 1159. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 qsvdec.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12975

.NET Framework: 1161. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 resampling_audio.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12978

.NET Framework: 1166. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 filtering_video.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12984

.NET Framework: 1169. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 demuxing_decoding.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12987

.NET Framework: 1170. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 transcode_aac.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/12991

.NET Framework: 1178. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 http_multiclient.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/13002

.NET Framework: 1180. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 remuxing.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/13006

.NET Framework: 1188. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 transcoding.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/13023

.NET Framework: 1190. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 vaapi_encode.c, vaapi_transcode.c 예제 포팅
; https://www.sysnet.pe.kr/2/0/13025

.NET Framework: 1191. C 언어로 작성된 FFmpeg Examples의 C# 포팅 전체 소스 코드
; https://www.sysnet.pe.kr/2/0/13026




C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 디코딩 예제(decode_video.c) - 두 번째 이야기

예전 글에서,

C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 디코딩 예제(decode_video.c)
; https://www.sysnet.pe.kr/2/0/12924

비디오 프레임을 디코딩 후 PGM 포맷으로 저장한 데이터가 정상적으로 보이지 않는다고 했는데요, 저도 이젠 나름 ^^ 지식이 쌓이다 보니 해당 문제를 다시 살펴볼 여유가 생겼습니다.

일단, 디코딩이 잘 되는지 "C# - ffmpeg(FFmpeg.AutoGen) - 비디오 프레임 디코딩" 글에서처럼 YUV 데이터를 RGB로 변환해 저장해 보았는데요,

while (ret >= 0)
{
    ret = ffmpeg.avcodec_receive_frame(pCodecContext, frame);
    if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN) || ret == ffmpeg.AVERROR_EOF)
    {
        return true;
    }
    else if (ret < 0)
    {
        Console.WriteLine("Error during decoding");
        return false;
    }

    AVPixelFormat format = (AVPixelFormat)frame->format;
    if (format == AVPixelFormat.AV_PIX_FMT_YUV420P)
    {
        byte* yData = frame->data[0];
        byte* uData = frame->data[1];
        byte* vData = frame->data[2];

        int yStride = frame->linesize[0];
        int uStride = frame->linesize[1];
        int vStride = frame->linesize[2];

        {
            byte Y, U, V;
            int r, g, b;
            Bitmap bitmap = new Bitmap(frame->width, frame->height, PixelFormat.Format24bppRgb);

            for (int y = 0; y < frame->height; y++)
            {
                for (int x = 0; x < frame->width; x++)
                {
                    Y = yData[yStride * y + x];
                    U = uData[uStride * (y / 2) + x / 2];
                    V = vData[vStride * (y / 2) + x / 2];

                    YUV2RGB_ByMS(Y, U, V, out r, out g, out b);
                    Color pixel = Color.FromArgb(0, r, g, b);
                    bitmap.SetPixel(x, y, pixel);
                }
            }

            bitmap.Save(Path.Combine(outfileDirPath, "yuv422p_" + pCodecContext->frame_number + ".bmp"));
        }
    }
}

위의 소스 코드를 실행해 보면, BMP 파일의 출력 결과가 깨져 나옵니다.

mepg1video_yuv_img_1.png

즉, 애당초 해당 코드는 정상적으로 video frame을 생성하지 못 하고 있었던 것입니다.




그래서 일단 decode_video.c가 av_parser_parse2를 이용해 디코딩하는 것을 "C# - ffmpeg(FFmpeg.AutoGen) - decoding 과정" 글에서처럼 av_read_frame/avcodec_send_packet/avcodec_receive_frame 방식으로 바꿨습니다.

동일하게 mpeg1video 코덱이 선택되었고,

{
    videoStream = av_context->streams[videoStreamIndex];
    videoContext = ffmpeg.avcodec_alloc_context3(videoDecoder);
    ret = ffmpeg.avcodec_parameters_to_context(videoContext, videoStream->codecpar);
    ret = ffmpeg.avcodec_open2(videoContext, videoDecoder, null);
}

string codecName = Marshal.PtrToStringAnsi(new IntPtr(videoDecoder->name));
Console.WriteLine(codecName); // mpeg1video

YUV420P 프레임을 정상적으로 해석해 BMP 파일로도 저장이 되었습니다. 그렇기 때문에 Y(Luma channel) 값을 pgm 파일로 저장하는 코드 역시 정상적으로 gray 포맷으로 출력이 되었습니다.

PGM File Viewer (browser-based)
; https://smallpond.ca/jim/photomicrography/pgmViewer/index.html

(사진은 유튜브 영상 "디에이드"의 "안다은" 님이고 사용을 허락받고 올립니다.)
mepg1video_yuv_img_2.png

그러니까 애당초 decode_video.c가 정상적으로 동작하지 않았던 코드였습니다. ^^;




참고로, 프레임을 명시적으로 AV_PIX_FMT_GRAY8로 변환해도 됩니다. 이를 위해 YUV to GRAY 변경을 SwsContext를 이용해 다음과 같이 수행할 수 있습니다.

SwsContext* sws_ctx = ffmpeg.sws_getContext(frame->width, frame->height, (AVPixelFormat)frame->format,
    frame->width, frame->height, AVPixelFormat.AV_PIX_FMT_GRAY8, ffmpeg.SWS_BICUBIC, null, null, null);

byte* pgray8 = (byte*)ffmpeg.av_calloc((ulong)(frame->width * frame->height), 1);
byte*[] ppgray8 = new byte*[1];
ppgray8[0] = pgray8;

int[] gray_stride = new int[1];
gray_stride[0] = frame->width;

int result = ffmpeg.sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, ppgray8, gray_stride);
// result == 1080 (1920 x 1080 인 경우)

Console.WriteLine($"saving frame {frame->coded_picture_number}");

string outputFile = Path.Combine(@"C:\temp\output", "noname_" + frame->coded_picture_number + ".pgm");
pgm_save(pgray8, frame->width, frame->width, frame->height, outputFile);

ffmpeg.av_free(pgray8);

ffmpeg.sws_freeContext(sws_ctx);

위에서는 pgm_save에 frame->data[0]을 전달하지 않고, sws_scale로 변환된 pgray8 포인터를 전달하고 있습니다. 당연히 저렇게 출력한 pgm 파일도 Y 채널만 출력한 pgm 파일과 동일한 흑백 영상이 나옵니다.

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




av_read_frame/avcodec_send_packet/avcodec_receive_frame 방식으로는 비디오가 잘 해석이 되는데, av_parser_parse2를 이용한 버전은 왜 안 되는 것일까요? 음... 이 정도까지는 아직 제 수준에서 이해할 수가 없군요. ^^

decode_video.c 파일의 버그인지, 아니면 원래 저렇게는 안 되는 것인지 알 수가 없습니다.

av_parser_parse2가 잘 동작하는 코드가 있긴 했습니다. 바로 오디오 파일을 디코딩하는 경우입니다.

C# - ffmpeg(FFmpeg.AutoGen)를 이용해 MP2 오디오 파일 디코딩 예제(decode_audio.c)
; https://www.sysnet.pe.kr/2/0/12933

C# - ffmpeg(FFmpeg.AutoGen)를 이용해 MP3 오디오 파일 인코딩/디코딩하는 예제
; https://www.sysnet.pe.kr/2/0/12939

하지만 비디오 디코딩에도 사용하는 소스 코드가 있는 걸로 봐서는,

MediaPlayer/MediaPlayer/FFmpegDecoder.cpp
; https://github.com/xc724655471/MediaPlayer/blob/13f0f518fd4e9e0ecb763ca1b23bf12bbd1ab249/MediaPlayer/FFmpegDecoder.cpp

분명히 디코딩 과정은 잘 동작했을 것으로 추정은 됩니다.




혹시나 싶어서, gray 포맷의 (mpeg1video는 오직 yuv420p만 지원하므로) h264 동영상을 전달하면 잘 동작할까요?

D:\media_sample> ffmpeg -i mp4video_sample2.mp4 -pix_fmt gray mp4video_sample2_gray.mp4

gray mp4 파일을 decode_video.c의 입력으로 전달하고 codec id만 AVCodecID.AV_CODEC_ID_H264로 바꿔보았는데요, 그래도 여전히 정상적인 비디오 프레임이 나오지는 않았습니다. 어쩌면 저 작업이 굳이 필요하지도 않았는데요, 왜냐하면 pix_fmt을 gray로 지정하긴 했지만 ffprobe로 확인해 보면 mp4video_sample2_gray.mp4는 여전히 yuv420p로 나오기 때문입니다. 즉, 영상만 흑백이고 pix_fmt은 변함없이 YUV420P입니다.

비록 pgm 저장은 우회해서 성공했지만, decode_vide.c 소스 코드는 끝내 미스터리로 남는군요. ^^




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 2/8/2022]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2022-02-08 12시45분
[이승준] 저도 av_parser_parse2 이건 사용해 보지 않았어서 뭔가 뭔가 싶었는데요.
찾아보니 기본적으로 컨테이너를 지원하지 않는 api로 보입니다. 즉 비디오/오디오 압축데이타만 지원한다는거죠.
그래서 오디오에서는 정상 동작한것으로 보입니다. 오디오 압축 파일은 컨테이너가 없거든요.
https://titanwolf.org/Network/Articles/Article?AID=d650b07d-cc80-4c8b-be0c-cf04e0da6435
참고링크 입니다.
아마도 비디오만 추출해서 테스트 해 보시면 정상동작할거라고 보여집니다.
[guest]
2022-02-08 01시42분
퇴근 후에 한번 해보겠습니다. ^^ 이제서야 decode_video.c의 수수께끼가 풀리는군요. 게다가 av_parser_parse2와 av_read_frame의 차이점도 자연스럽게 이해되고. ^^
정성태

... 121  122  123  124  125  126  127  128  129  130  131  132  133  [134]  135  ...
NoWriterDateCnt.TitleFile(s)
1705정성태7/2/201435064VC++: 78. 보이어-무어(Boyer-Moore) 알고리즘이 정말 빠를까? [6]파일 다운로드1
1704정성태7/2/201421648.NET Framework: 447. w3wp.exe AppPool 재생(recycle)하는 방법 정리
1703정성태7/2/201422495.NET Framework: 446. Assembly.Load를 이용해 GAC에 등록된 어셈블리를 로드하는 방법 [1]파일 다운로드1
1702정성태6/23/201422258Phone: 11. Xamarin.Forms - 2. XAML을 이용한 페이지 개발파일 다운로드1
1701정성태6/23/201434444개발 환경 구성: 229. .NET Reflector + Reflexil 도구를 이용해 DLL 코드 변경 [4]
1700정성태6/23/201421297VS.NET IDE: 89. Visual Studio에서 기본 제공되는 성능 프로파일 [2]
1699정성태6/22/201424091Phone: 10. Xamarin.Forms - 1. Forms 시작하기 [2]파일 다운로드1
1698정성태6/22/201426067.NET Framework: 445. [부연 설명] 쉬운 C# 코드를 어럽게 이해하기 [2]
1697정성태6/22/201421356VS.NET IDE: 88. Visual Studio에서 직접 컴파일하는 IL 언어 확장 도구 - IL Support
1696정성태6/22/201421164.NET Framework: 444. clojure와 C#을 통해 이해하는 Sequence와 Vector 형식의 차이점 [1]
1695정성태6/21/201420190개발 환경 구성: 228. PowerShell ISE에서 (입력 기능이 있는) 콘솔 응용 프로그램을 시작하는 방법
1694정성태6/21/201421341개발 환경 구성: 227. 닷넷 용 ClojureCLR 개발환경 설정
1693정성태6/20/201421656개발 환경 구성: 226. Clojure 언어의 윈도우 개발환경 설정
1692정성태6/19/201432247오류 유형: 231. Visual Studio 2013 한글 버전 설치 오류 - The form specified for the subject is not one supported or known by the specified trust provider
1691정성태6/18/201427461개발 환경 구성: 225. 유닉스 계열의 tail 명령어가 제공되는 PowerShell [1]
1690정성태6/18/201430253개발 환경 구성: 224. DirectShow 예제 구하는 방법 [3]
1689정성태6/18/201427095오류 유형: 230. C++ 가변 인자 사용시 va_start 파라미터 전달 방법 [2]
1688정성태6/15/201420641오류 유형: 229. 갤럭시 노트 3 환경에서 Xamarin 앱 배포 충돌
1687정성태6/15/201426655개발 환경 구성: 223. PowerShell로 Visual Studio 빌드 스크립트 작성파일 다운로드1
1686정성태6/12/201424356Windows: 96. 윈도우 8 - 그림 암호를 이용해 로그인 시 지연 현상을 해결하는 방법 [1]
1685정성태6/10/201431116.NET Framework: 443. 자바 8과 C#의 람다(Lambda) 지원에 대한 비교 [12]
1684정성태6/9/201441286.NET Framework: 442. C# - 시스템의 CPU 사용량 및 프로세스(EXE)의 CPU 사용량 알아내는 방법 [5]파일 다운로드1
1683정성태6/2/201420850오류 유형: 228. CLR4 보안 - yield 구문 내에서 SecurityCritical 메서드 사용 불가 [2]파일 다운로드1
1682정성태6/1/201426066.NET Framework: 441. .NET CLR4 보안 모델 - 3. CLR4 보안 모델에서의 APTCA 역할파일 다운로드2
1681정성태6/1/201421935.NET Framework: 440. .NET CLR4 보안 모델 - 2. 샌드박스(Sandbox)을 이용한 보안 [2]파일 다운로드1
1680정성태6/1/201421410.NET Framework: 439. .NET CLR4 보안 모델 - 1. "Security Level 2"란?파일 다운로드1
... 121  122  123  124  125  126  127  128  129  130  131  132  133  [134]  135  ...