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 파일의 출력 결과가 깨져 나옵니다.
즉, 애당초 해당 코드는 정상적으로 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
(사진은
유튜브 영상 "디에이드"의 "안다은" 님이고 사용을 허락받고 올립니다.)
그러니까 애당초 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 소스 코드는 끝내 미스터리로 남는군요. ^^
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]