2012년 4월 27일 금요일

ffmpeg 스케일러와 DirectDraw 를 이용한 YUV -> RGB 고속변환 출력 - YUV -> RGB Converting by ffmpeg and DirectDraw

YUV420->RGB32 변환출력의 경우 보통 이전에 포스팅된 YUV->RGB 변환방식을 사용하면된다. YUV420 버퍼를 YUV 평면에 그데로 복사하고 primary 평면에 blt 를 하면 yuv->rgb 변환과 스케일링을 동시에 고속으로 수행할 수 있다.

이번에 사용하는 방법은 YUV420 버퍼를 일반 RGB32 offscreen 평면에 스케일링한 후에  primary 평면에 blt 하는 방법이다.
이 방식은 기존 YUV->RGB 변환 방식과 성능이 동일하게 나오지만 비디오카드의 하드웨어 가속기를 사용하지않기 때문에 다른 프로세스가 하드웨어 가속기를 점유하고 있을경우에도 문제없이 사용할수 있다.

ffmpeg 스케일러 사용법과 DirectDraw 사용방법은 이전 포스팅 참조

< DirectDraw 평면 생성 >
...

ZeroMemory( &ddsd, sizeof( ddsd ) );
ddsd.dwSize         = sizeof( ddsd );
ddsd.dwFlags        = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

ddsd.dwWidth        = targetWidth;
ddsd.dwHeight       = targetHeight;

if ((hr=pDD->CreateSurface(&ddsd, &lpBackBuffer, NULL)) != DD_OK)
{
printf("failed to create back buffer surface (hr:0x%x)\n", hr);
return -1;
}

// 스케일러 생성
sws = sws_getContext(srcWidth, srcHeight, PIX_FMT_YUV420P, targetWidth, targetHeight, PIX_FMT_RGB32, SWS_BILINEAR, NULL, NULL, NULL);
...

< primary 평면에 출력 >

int VideoDDraw::draw(AVFrame *pPicture, RECT *rectTarget)
...
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
if ((hr=lpBackBuffer->Lock(NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL)) == DD_OK)
{
        // offscreen 평면에 복사 (스케일링과 동시에 복사)
LPBYTE lpSurf = (LPBYTE)ddsd.lpSurface;
LPBYTE ptr = lpSurf;
unsigned char *src_buf[4];
src_buf[0] = pPicture->data[0];
src_buf[1] = pPicture->data[1];
src_buf[2] = pPicture->data[2];
src_buf[3] = NULL;

int src_stride[4];
src_stride[0] = pPicture->linesize[0];
src_stride[1] = pPicture->linesize[1];
src_stride[2] = pPicture->linesize[2];
src_stride[3] = 0;

unsigned char *dst_buf[4];
dst_buf[0] = ptr;
dst_buf[1] = NULL;
dst_buf[2] = NULL;
dst_buf[3] = NULL;
int dst_stride[4];
dst_stride[0] = ddsd.lPitch;
dst_stride[1] = ddsd.lPitch;
dst_stride[2] = ddsd.lPitch;
dst_stride[3] = ddsd.lPitch;

        // 스케일링(변환된 데이터가 들어감)
sws_scale(sws, src_buf, src_stride, 0, h, dst_buf, dst_stride);

lpBackBuffer->Unlock(NULL);
}
...
// primary 평면에 blt
hr = lpPrimary->Blt(rectTarget, lpBackBuffer, &rectSrc, DDBLT_ASYNC, NULL);

댓글 6개:

  1. 좋은 글을 보다 질문이 있어서 몇자 적어봅니다.
    주인장님은 yuv->rgb로 변환하여 속도를 개선하였는데, 혹시 YUV420->YUV422변환할때 이런 방식으로 사용해도 속도를 개선할 수 있나요?

    답글삭제
    답글
    1. 이 포스팅은 yuv420->rgb32 변환이 목적이 아니라 directdraw를 이용한 영상출력이 목적입니다. 영상출력이 아닌 변환이 목적이라면 ffmpeg 스케일러만 사용하면 됩니다. 만약 directdraw로 변환하려면 이전 포스팅참조하셔서 yuv420 offscreen 평면 -> yuv422 offscreen 평면 으로 blt 하면됩니다. 참고로 위 두 방법의 성능은 동일합니다.

      삭제
  2. 주인장님의 유용한 포스팅 감사합니다.
    rgb32->yuv420으로 ffmpeg의 스케일러를 사용하고 있는데, 성능이 너무 느리네요.
    이런경우 속도를 개선할수 있는 방법이 있을까요?

    답글삭제
    답글
    1. ffmpeg 빌드할때 어셈블리가 disable 됐거나 mmx/sse가 disable 된 경우가 아니라면 ffmpeg 스케일러는 최대성능을 냅니다. 성능을 더 올리려면 다른 스케일러를 찾거나 스케일러를 직접 구현해야합니다.

      삭제
  3. 이렇게 빨리 답변을 해주시다니 감사합니다. 컴파일 옵션을 봐야겠네요.
    좋은하루되세요. ^^

    답글삭제