2011년 7월 8일 금요일

DirectDraw를 이용한 YUV -> RGB 고속 변환출력 - YUV -> RGB Converting by using DirectDraw

< DirectDraw YUV Surface 생성 - creating directdraw surfaces >

DDPIXELFORMAT g_ddpfFormats[] = 
{{sizeof(DDPIXELFORMAT), DDPF_FOURCC,MAKEFOURCC('Y','V','1','2'),0,0,0,0,0}}; 
...
IDirectDraw7 *pDD;
IDirectDrawSurface7 *lpPrimary;
IDirectDrawClipper      *lpClipper;
IDirectDrawSurface7   *lpYUVBuffer;
int g_width, g_height;

initDraw(HWND hWnd, int width, int height)
{
HRESULT hr;

g_width = width;
g_height = height;

hr = DirectDrawCreateEx(NULL, (void **)&pDD, IID_IDirectDraw7, NULL);
if (FAILED(hr)) {
TRACE("failed to create directdraw device (hr:0x%x)\n", hr);
return -1;
}

hr = pDD->SetCooperativeLevel(hWnd, DDSCL_NORMAL);
if(FAILED(hr)) {
TRACE("failed to SetCooperativeLevel (hr:0x%x)\n", hr);
return -1;
}


DDSURFACEDESC2 ddsd;

/* creating primary surface (RGB32) */
ZeroMemory( &ddsd, sizeof( ddsd ) );
ddsd.dwSize         = sizeof( ddsd );
ddsd.dwFlags        = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

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

/* creating clipper */
hr = pDD->CreateClipper(0, &lpClipper, NULL);
if (hr != DD_OK)
{
TRACE("failed to create create clipper (hr:0x%x)\n", hr);
pDD->Release();
return -1;
}
hr = lpClipper->SetHWnd(0, hWnd);
if (hr != DD_OK)
{
TRACE("failed to clippter sethwnd (hr:0x%x)\n", hr);
lpClipper->Release();
lpPrimary->Release();
pDD->Release();
return -1;
}

hr = lpPrimary->SetClipper(lpClipper);
if (hr != DD_OK)
{
TRACE("failed to set clipper (hr:0x%x)\n", hr);
lpClipper->Release();
lpPrimary->Release();
pDD->Release();
return -1;
}


/* creating yuv420 surface */
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth = width;
ddsd.dwHeight = height;

memcpy(&ddsd.ddpfPixelFormat, &g_ddpfFormats[0], sizeof(DDPIXELFORMAT));

if ((hr=pDD->CreateSurface(&ddsd, &lpYUVBuffer, NULL)) != DD_OK)
{
TRACE("failed to create yuv buffer surface\n");
return -1;
}
...
}


< Primary Surface 로 변환해서 출력 - yuv surface -> primary surface blt >

int draw(RECT *rectTarget, AVFrame *pPicture)
{
HRESULT hr;

int w = g_width;
int h = g_height;
...
DDSURFACEDESC2 ddsd;

ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
if ((hr=lpYUVBuffer->Lock(NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL)) == DD_OK)
{
LPBYTE lpSurf = (LPBYTE)ddsd.lpSurface;
LPBYTE ptr = lpSurf;
int t_stride = pPicture->linesize[0];
int i;
for (i=0; i < h; i++) {
memcpy(ptr+i*ddsd.lPitch, pPicture->data[0]+i*t_stride, w);
}
ptr += ddsd.lPitch*h;
for (i=0; i < h/2; i++) {
memcpy(ptr+i*ddsd.lPitch/2, pPicture->data[2]+i*t_stride/2, w/2);
}
ptr += ddsd.lPitch*h/4;
for (i=0; i < h/2; i++) {
memcpy(ptr+i*ddsd.lPitch/2, pPicture->data[1]+i*t_stride/2, w/2);
}

lpYUVBuffer->Unlock(NULL);
}

RECT rectSrc;
rectSrc.top = 0;
rectSrc.left = 0;
rectSrc.right = w;
rectSrc.bottom = h;

hr = lpPrimary->Blt(rectTarget, lpYUVBuffer, &rectSrc, DDBLT_ASYNC, NULL);
}

< ddraw resource release >


void release()
{
if (pDD != NULL)
{
pDD->Release();
pDD = NULL;
}

if (lpPrimary != NULL)
{
lpPrimary->Release();
lpPrimary = NULL;
}

if (lpYUVBuffer != NULL)
{
lpYUVBuffer->Release();
lpYUVBuffer = NULL;
}
}



위 소스에서 AVFrame 은 ffmpeg 에서 사용하는 구조체이고 data[0], data[2], data[1] 는 각각 yuv(yuv420) 값을 가진다.
분리된(non-interleaved) yuv 값을 가진 버퍼를 yuv surface에 memcpy 로 적절하게 복사한 다음 primary surface 로 blt 하면 하드웨어 가속기를 이용한 고속복사를 통해 영상 이미지를 출력할 수 있다.
여기서 primary surface의 픽셀포멧은 RGB32 인데 다른 픽셀포멧의 surface 에다 blt 시켜서 변환시킬수도 있을것같다.

댓글 17개:

  1. 안녕하세요. 올려주신 글덕분에 많은 도움이 되었습니다. 하나 궁금한것이 있는데요, yuv 서페이스 보다 작은 크기의 yuv 데이터를 yuv 서페이스에 직접 복사하고싶은데요 같은크기일경우는 데이터가 정상적으로 복사되는데 크기가 다른경우에 제대로안되더라구요 ㅜ
    pitch 값이 달라져서그런거같은데 조언부탁드립니다

    답글삭제
    답글
    1. 원본 이미지를 타겟 서피스 사이즈로 스케일링 한다음 복사해주면 됩니다. 스케일링은 ffmpeg 스케일러를 사용해도되고 다른 방법으로는 같은 크기의 yuv 서피스로 복사한다음 타겟 서피스로 blt 시키면 directdraw 가 스케일링해서 복사해줍니다.

      삭제
    2. 답변 감사드립니다. 저런방법 말고 lock한후 직접 복사하는 방법으로는 크기가 다를경우 불가능한건가요??

      삭제
    3. 원본 이미지와 서피스의 사이즈가 다르기 때문에 스케일링을 하지않으면 이미지가 서피스의 일부분에 표시되던지 일부분만 표시되던지 두 가지 경우가 발생하게됩니다. 이런 결과를 원한다면 피치값을 계산해서 복사해주어야합니다

      삭제
    4. 친절한 답변감사드립니다.
      계속 질문드려서 죄송한데, 하나만 더여쭤봐도되런지요...ㅜ ㅜ
      윈도우 모드에서는 클리퍼를 설정해야 하는 걸로아는데
      여러개의 뷰에서 이미지를 출력하는 경우에는 클리퍼를 어떻게 설정해야되나요? 클리퍼설정을 안하면 화면이 멈추고 그러더라구요 ㅜ

      삭제
    5. 클리퍼에 윈도우 핸들을 설정하면 해당 윈도우에 영상이 출력됩니다. 같은 이미지를 여러개의 윈도우에 출력하려면 윈도우 마다 서피스를 만들고 각 윈도우별로 클리퍼 설정을 한다음 이미지를 각 윈도우의 서피스에 복사해주면됩니다

      삭제
  2. 제가 잘못생각하고있는건지 출력을 위해서는 Primary로 Blt를 해주어야할텐데요, Primary영역은 오직 하나만 가질수 있는거 아닌지요...? 각 윈도우마다 Primary영역을 가질수있다면 말씀하신방법으로 가능할거같은데요 ㅠㅠ 햇갈리네요 ㅠ

    답글삭제
    답글
    1. primary 서피스는 한 개이고 여러개의 서피스(lpYUVBuffer)가 blt를 하는 방식으로 구현해야합니다

      삭제
  3. 답변 달아주셔서 감사합니다. 각 서피스마다 클리퍼를 설정하고 서피스를 primary 영역으로 blt 하는 방식으로 해야한다는 말씀이신지요...? 그렇다면 Primary 영역은 클리퍼 설정을 최상위 부모윈도우 핸들로 해야하나여..?ㅠ

    답글삭제
    답글
    1. 제가 잘못 생각했네요. primary 서피스도 윈도우 갯수만큼 설정해야합니다. 각 클리퍼도 해당 윈도우 핸들로 설정하고 각 back buffer 서피스를 primary 서피스로 blt 하면되겠네요

      삭제
  4. 답글 감사합니다 ^_^ 많은 도움이 되었습니다.

    답글삭제
    답글
    1. primary 서피스는 모니터당 한개만 생성되네요. 위의 방법말고 다른 방법을 찾아야할것 같습니다..

      삭제
    2. 네.. 최근에 시도해봤는데.. 이미 생성되있다고 생성자체가 안되네요....ㅎㅎ

      삭제
    3. 서피스 마다 클리퍼를 생성하고 주표면으로 출력전에 해당 서피스의 클리퍼를 주표면에 Set하고 출력하니까 됬습니다 ㅎㅎ

      삭제
  5. hr = lpPrimary->Blt(rectTarget, lpYUVBuffer, &rectSrc, DDBLT_ASYNC, NULL);
    이렇게 blt 된 RGBA 픽셀을 어떻게 가져오나요
    변환된 픽셀을 텍스쳐로 변환 하고 싶은데
    방법을 모르겠네요 ㅜㅜ

    답글삭제
    답글
    1. 기본평면에 blt된 데이터를 가져올수는 없고요. YUV->RGBA 변환은 스케일러를 사용하면됩니다.

      삭제