Tistory View

Android Develop/image

안드로이드 YV12, stride와 RGB 변환

God Dangchy What should I do? 2019. 8. 6. 15:26

YV12 구조

안드로이드에서 사용하는 YV12는 YUV각각의 Plane이 다 분리되어 있고, U와 V Plane은 폭과 높이가 모두 이미지 크기의 반이다. 픽셀의 색은 다음과 같이 배경색의 조합으로 구성된다.

 

 

YV21 6x4 이미지(padding을 고려안함)

[Y00][Y01][Y02][Y03][Y04][Y05]

[Y06][Y07][Y08][Y09][Y10][Y11]

[Y12][Y13][Y14][Y15][Y16][Y17]

[Y18][Y19][Y20][Y21][Y22][Y23]

+

[V0][V1][V2]

[V2][V3][V2]

+

[U0][U1][U2]

[U2][U3][U2]

 

 
 
 

YV12 STRIDE

NV21은 stride값을 계산할 필요없이 모든 픽셀이 붙어있어, padding이 전혀없다. 하지만 YV12는 NV21과 달리 Padding을 계산해 줘야 한다.
 
 
계산식은 다음과 같다. 여기서 w와 h는 짝수로 한정한다.
y_stride   = ALIGN(w, 16) 
y_size     = stride * height
c_stride   = ALIGN(stride/2, 16)   // U and V plane stride
c_size     = c_stride * height/2
size       = y_size + c_size * 2  // 전체 YV12 바이트 크기
v_offset   = y_size
u_offset   = y_size + c_size
 
 
식에서 보듯 y_stride와 c_stride값이 따로 존재하며 둘다 16bytes로 align되어야 한다. 이 align으로 인해 padding이 생기게 된다.
 
위의 식을 다음과 같이 함수로 미리 만들어 두면 편할 것이다.
 
Demension info in C/C++
struct YV12DemensionInfo {

    int w;              // 이미지 폭
    int h;              // 이미지 높이

    int strideY;        // Y Plane stride
    int strideC;        // UV Plane stride

    int offsetV;        // V의 시작 위치(오프셋)
    int offsetU;        // U의 시작 위치(오프셋)

    int yPlaneW;        // Y Plane 폭   : 이미지 폭과 같다.
    int yPlaneH;        // Y Plane 높이 : 이미지 높이와 같다.

    int cPlaneW;        // UV Plane 폭
    int cPlaneH;        // UV Plane 높이

    int yPlaneSize;     // Y Plane  바이트크기
    int uvPlaneSize;    // UV Plane  바이트크기
    int totalBytes;     // YV12의 전체 바이트 크기

};

void computeYVDemensionInfo( YV12DemensionInfo* p, int w, int h )
{
    int pad;
    int strideYPlane;
    int strideCPlane; // UV plane stide(Cr,Cb)

    pad = 16 - w % 16;
    if( pad == 16 ) {
        pad = 0;
    }

    strideYPlane = w + pad;

    pad = 16 - ( strideYPlane / 2 ) % 16;
    if( pad == 16 ) pad = 0;
    strideCPlane = ( strideYPlane / 2 ) + pad;

    p->w           = w;
    p->h           = h;
    p->yPlaneW     = w;
    p->yPlaneH     = h;
    p->cPlaneW     = strideYPlane / 2;
    p->cPlaneH     = h / 2;
    p->strideY     = strideYPlane;
    p->strideC     = strideCPlane;
    p->offsetV     = strideYPlane * h;
    p->offsetU     = strideYPlane * h + strideCPlane * (h / 2);
    p->yPlaneSize  = strideYPlane * h;
    p->uvPlaneSize = strideCPlane * (h / 2);
    p->totalBytes  = p->yPlaneSize + p->uvPlaneSize * 2;
}
 
 
 
 
실제 적당한 예로 실제 계산을 해보자. 176x144의 이미지가 있다면 
strIdeYPlane = 176 // 16 * 11 나머지가 없으니 padding은 없다.

width = 176;
height = 144;
yPlaneW = 176;
yPlaneH = 144;
cPlaneW = 176 / 2 = 88;
cPlaneH = 144 / 2 = 72;

88( 16 * 5 + 8 ) % 16 
strideC  => = 88 + ( 16 - 8 ) = 96;
offsetV = 176 * 144= 25344;
offsetU =176 * 144 + 96 * 72 = 25344 + 6912 = 32256;
yPlanSize = 176 * 144 = 25344;
uvPlaneSize = 96 * 72;
totalBytes = 25344 + 6912 * 2 = 39168;
 
 
 
 
만약 Camera가 previewSize 176x144크기를 지원한다면, 실제 위 계산방식을 사용해야 밀림이나 깨짐없이 처리되는 것을 볼 수 있다.
 
 
stride를 계산하지 않을 경우
 

 

YV12 구조 패딩고려

실제 카메라에서 onPreviewFrame을 통해서 6X4의 이미지가 넘어온다면 넘어온 데이터는 다음과 같다.

 

[Y00][Y01][Y02][Y03][Y04][Y05][padding 10개]

[Y06][Y07][Y08][Y09][Y10][Y11][padding 10개]

[Y12][Y13][Y14][Y15][Y16][Y17][padding 10개]

[Y18][Y19][Y20][Y21][Y22][Y23][padding 10개]

+

[V0][V1][V2][padding 13개]

[V2][V3][V2][padding 13개]

+

[U0][U1][U2][padding 13개]

[U2][U3][U2][padding 13개]

 

 

 

 

 

 

YV12를 RGB로 변환하는 코드는 다음과 같다.
 
The code of Converting YV12 to RGB(A)
inline uint8_t clamp16bits( int v )
{
    v >>= 16;

    // most case 1 ~ 255
    if( ( v & 0xffffff00 ) == 0 ) {
        return v;
    }

    if( v <= 0 ) return 0;

    return 255;
}

void image_yv12_to_rgba( uint8_t* dst, uint8_t* yv12, int w, int h, int nThread )
{

    const int strideDst  = w*4; // in byte

    YV12DemensionInfo demInfo;
    computeYVDemensionInfo(  &demInfo, w, h );

    for( int y = 0 ; y < demInfo.cPlaneH ; y++ )
    {
        uint8_t* pSrcY1 = yv12 + ( y * 2 ) * demInfo.strideY;
        uint8_t* pSrcY2 = pSrcY1 + demInfo.strideY;


        uint8_t* pSrcV = yv12 + demInfo.offsetV + y * demInfo.strideC;
        uint8_t* pSrcU = yv12 + demInfo.offsetU + y * demInfo.strideC;


        uint8_t* pd1  = dst + y * 2 * strideDst;
        uint8_t* pd2  = pd1 + strideDst;


        for( int x = 0 ; x < demInfo.cPlaneW ; x++ )
        {
            const int cU = (int)pSrcU[0] - 128;
            const int cV = (int)pSrcV[0] - 128;

            const int f1 = 89831 * cV; // 1.370705F * 65536 * ( (int)cV - 128 );
            const int f2 = -45744 * cV -22127 * cU;
            const int f3 = 113538 * cU; // 1.732446F * 65536 * ( (int)cU - 128 );

            int _y;

            _y = pSrcY1[0] << 16;
            pd1[0] = clamp16bits( _y + f1 ); // r
            pd1[1] = clamp16bits( _y + f2 ); // g
            pd1[2] = clamp16bits( _y + f3 ); // b
            pd1[3] = 0xff; // a

            _y = pSrcY1[1] << 16;
            pd1[4] = clamp16bits( _y + f1 );
            pd1[5] = clamp16bits( _y + f2 );
            pd1[6] = clamp16bits( _y + f3 );
            pd1[7] = 0xff;


            _y = pSrcY2[0] << 16;
            pd2[0] = clamp16bits( _y + f1 );
            pd2[1] = clamp16bits( _y + f2 );
            pd2[2] = clamp16bits( _y + f3 );
            pd2[3] = 0xff;

            _y = pSrcY2[1] << 16;
            pd2[4] = clamp16bits( _y + f1 );
            pd2[5] = clamp16bits( _y + f2 );
            pd2[6] = clamp16bits( _y + f3 );
            pd2[7] = 0xff;


            pSrcY1+=2;
            pSrcY2+=2;

            pSrcU++;
            pSrcV++;

            pd1 += 8;
            pd2 += 8;
        }

    }
}

세로는 두줄(두칸)씩 처리했다.

 

 

 

Android NDK Bitmap 메모리에 접근하기

Replies
Reply Write