Tistory View
반응형
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
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크기를 지원한다면, 실제 위 계산방식을 사용해야 밀림이나 깨짐없이 처리되는 것을 볼 수 있다.
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 Develop > image' 카테고리의 다른 글
안드로이드 ImageView의 ScaleType (0) | 2019.12.04 |
---|---|
Android YV12, stride and RGB conversion (0) | 2019.08.06 |
Android NDK Access memory of Bitmap directly (5) | 2019.08.05 |
Android NDK Bitmap 메모리에 접근하기 (2) | 2019.08.05 |
늘 괴롭히는 이미지의 stride (2) | 2017.10.30 |
Replies
NOTICE
RECENT ARTICLES
RECENT REPLIES
- Total
- Today
- Yesterday
LINK
TAG
- 재태크
- 전기세
- 사용료
- 컴퓨트셰이더
- 아끼는 법
- 컴퓨트쉐이더
- choreographer
- texture
- 텍스처
- gpgpu
- 전기료
- ComputeShader
- 블로그
- 에어컨
- 적금
- 에어콘
- 경제보복
- 예금
- 금리
- TTS
- 애드센스
- 안드로이드
- Android
- 재테크
- OpenGLes
- 전기요금
- 티스토리
- 공유 컨텍스트
- OpenGL ES
- 애드핏
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Article Box