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의 차이점도 자연스럽게 이해되고. ^^
정성태

... 106  107  108  109  110  111  112  113  114  115  116  117  118  [119]  120  ...
NoWriterDateCnt.TitleFile(s)
10949정성태4/28/201619926.NET Framework: 575. SharedDomain과 JIT 컴파일파일 다운로드1
10948정성태4/28/201623888.NET Framework: 574. .NET - 눈으로 확인하는 SharedDomain의 동작 방식 [3]파일 다운로드1
10947정성태4/27/201621761.NET Framework: 573. .NET CLR4 보안 모델 - 4. CLR4 보안 모델에서의 조건부 APTCA 역할파일 다운로드1
10946정성태4/26/201624552VS.NET IDE: 106. Visual Studio 2015 확장 - INI 파일을 위한 사용자 정의 포맷 기능 (Syntax Highlighting)파일 다운로드1
10945정성태4/26/201618333오류 유형: 327. VSIX 프로젝트 빌드 시 The "VsTemplatePaths" task could not be loaded from the assembly 오류 발생
10944정성태4/22/201619552디버깅 기술: 80. windbg - 풀 덤프 파일로부터 텍스트 파일의 내용을 찾는 방법
10943정성태4/22/201624404디버깅 기술: 79. windbg - 풀 덤프 파일로부터 .NET DLL을 추출/저장하는 방법 [1]
10942정성태4/19/201619714디버깅 기술: 78. windbg 사례 - .NET 예외가 발생한 시점의 오류 분석 [1]
10941정성태4/19/201619630오류 유형: 326. Error MSB8020 - The build tools for v120_xp (Platform Toolset = 'v120_xp') cannot be found.
10940정성태4/18/201622917Windows: 116. 프로세스 풀 덤프 시간을 줄여 주는 Process Reflection [3]
10939정성태4/18/201623923.NET Framework: 572. .NET APM 비동기 호출의 Begin...과 End... 조합 [3]파일 다운로드1
10938정성태4/13/201623485오류 유형: 325. 파일 삭제 시 오류 - Error 0x80070091: The directory is not empty.
10937정성태4/13/201631686Windows: 115. UEFI 모드로 윈도우 10 설치 가능한 USB 디스크 만드는 방법
10936정성태4/8/201642422Windows: 114. 삼성 센스 크로노스 7 노트북의 운영체제를 USB 디스크로 새로 설치하는 방법 [3]
10935정성태4/7/201626696웹: 32. Edge에서 Google Docs 문서 편집 시 한영 전환키가 동작 안하는 문제
10934정성태4/5/201625392디버깅 기술: 77. windbg의 콜스택 함수 인자를 쉽게 확인하는 방법 [1]
10933정성태4/5/201631031.NET Framework: 571. C# - 스레드 선호도(Thread Affinity) 지정하는 방법 [8]파일 다운로드1
10932정성태4/4/201623310VC++: 96. C/C++ 식 평가 - printf("%d %d %d\n", a, a++, a);
10931정성태3/31/201623584개발 환경 구성: 283. Hyper-V 내에 구성한 Active Directory 환경의 시간 구성 방법 [3]
10930정성태3/30/201621553.NET Framework: 570. .NET 4.5부터 추가된 CLR Profiler의 실행 시 Rejit 기능
10929정성태3/29/201631662.NET Framework: 569. ServicePointManager.DefaultConnectionLimit의 역할파일 다운로드1
10928정성태3/28/201637358.NET Framework: 568. ODP.NET의 완전한 닷넷 버전 Oracle ODP.NET, Managed Driver [2]파일 다운로드1
10927정성태3/25/201626564.NET Framework: 567. System.Net.ServicePointManager의 DefaultConnectionLimit 속성 설명
10926정성태3/24/201626116.NET Framework: 566. openssl의 PKCS#1 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법 [10]파일 다운로드1
10925정성태3/24/201620409.NET Framework: 565. C# - Rabin-Miller 소수 생성 방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 - 두 번째 이야기파일 다운로드1
10924정성태3/22/201621096오류 유형: 324. Visual Studio에서 Azure 클라우드 서비스 생성 시 Failed to initialize the PowerShell host 에러 발생
... 106  107  108  109  110  111  112  113  114  115  116  117  118  [119]  120  ...