2019. 12. 13. 17:35ㆍProgramming/Android
YUV란?
YUV(YIQ/YCbCr/YPbPr)는 색을 구성하는 방법 중 하나로 밝기(Y)와 청색 색차(U), 적색 색차(V) 정보로 색을 구성한다. 여기서 Y 신호만 받는다면 흑백이 된다. 여러 번 복제한 VHS 테이프나 방송 상태가 좋지 못한 채널에서 흑백으로 보이는 것도 이 때문. Lab과 마찬가지로 인간이 색을 인식하는 방식으로 구성되었다. - 나무위키 발췌(https://namu.wiki/w/YUV)
사실 발췌는 해왔지만 정확하게 아는 것은 아니다. 흑백 TV의 위치를 컬러 TV가 조금씩 대체해가던 과도기 시절, RGB를 흑백 TV에 송출하기 어렵기 때문에 만들어진 색 공간 개념이라고 한다. RGB보다 전송에 용이하기 때문에 흑백 TV가 더 이상 사용되지 않는 요즈음에도 많이 사용하고 있다고 한다.
YUV 색공간에 대한 설명은 이 이상 할 생각이 없다. 사실 글을 작성하는 본인이 색공간에 대한 지식은 발췌한 내용보다도 적고, 당장 YUV만 검색하더라도 수두룩하게 많이 나오기 때문이다. 뭣보다 이 글을 작성하는 이유는, libyuv
를 사용하여 컬러 스페이스 컨버젼
(이하 CSC)을 하기 위해 상당히 긴 시간동안 크로미움을 가지고 삽질을 했기 때문이다. 사실 소스코드만 받으면 그럴 필요도 없었는데 말이다.
YUV2RGB와 ARM8-V64A
일반적으로 카메라에서 수신된 영상을 디코딩하여 프레임을 얻게되면, YUV 포멧으로 되어있다. 이를 사용하기 위해서는 RGB로 변환해야 하는데, 동작하는 코드 자체는 크게 어렵지 않다. 레거시 앱에서는 PINK NOISE PRODUCTIONS
의 YUV2RGB
를 사용하여 CSC
를 하고 있었는데, .S
로 작성된 어셈블리 코드가 있기 때문에 하드웨어 가속을 사용할 수 있다. YUV2RGB
는 상당히 오래된 라이브러리기 때문에 arm64-v8
칩셋에 대한 고려는 되어있지 않고, 만약 YUV2RGB
를 사용하는 레거시 코드를 64bits
로 빌드하려고 하면 에러를 뿜뿜하는 안드로이드 스튜디오를 발견하게 된다.
Arm® Architecture Reference Manual Armv8, for Armv8-A architecture profile
arm7
에서arm8
로 넘어가면서 삭제된 명령어들이 꽤 되기 때문에,arm7
용으로 작성된.S
파일을 사용하려고 하면 에러를 뿜뿜하게 된다.
앞서 말했듯이 YUV
를 RGB
로 변환하는 방법은 어렵지 않다. 물론 YUV
포멧에도 여러가지가 있고, RGB
의 포멧도 여러가지가 있지만... 대부분 라이브러리로 구현되어있기 때문에, 인터넷을 참조하여 코드를 작성하더라도 크게 문제는 없다. 나의 경우에는 64bits
에 임시책으로 Tensorflow
에 포함되어있던 .cc
코드를 사용했다. 아래의 코드는 깃에 올라온 Tensorflow
의 예제 코드로, YUV420
을 ARGB8888
로 변환하는 코드다.
// Accepts a YUV 4:2:0 image with a plane of 8 bit Y samples followed by an
// interleaved U/V plane containing 8 bit 2x2 subsampled chroma samples,
// except the interleave order of U and V is reversed. Converts to a packed
// ARGB 32 bit output of the same pixel dimensions.
void ConvertYUV420SPToARGB8888(const uint8_t* const yData,
const uint8_t* const uvData,
uint32_t* const output, const int width,
const int height) {
const uint8_t* pY = yData;
const uint8_t* pUV = uvData;
uint32_t* out = output;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int nY = *pY++;
int offset = (y >> 1) * width + 2 * (x >> 1);
#ifdef __APPLE__
int nU = pUV[offset];
int nV = pUV[offset + 1];
#else
int nV = pUV[offset];
int nU = pUV[offset + 1];
#endif
*out++ = YUV2RGB(nY, nU, nV);
}
}
}
위의 C코드를 사용하면 색변환이 되서 정상적으로 동작하지만, 하드웨어 가속이 적용되지 않아 처리 속도가 느리다. 프레임을 한 두장 정도 처리하는데는 문제가 없지만, 연속으로 처리하는 경우에는 문제가 발생한다. 2019년 11월부터 64bits
를 지원하지 않는 어플리케이션은 스토어에 등록할 수 없으므로, 빠른 속도로 CSC
를 처리하기 위해서는 다른 방법이 필요하다.
LIBYUV
LIBYUV
는 YUV
/RGB
변환 및 스케일링을 제공하는 라이브러리로, 간단하게 적용하여 사용할 수 있다. 뿐만 아니라 어셈블리 코드가 포함되어있어, 하드웨어 가속이 적용되므로 보다 빠른 처리를 기대할 수 있다. 만약 YUV2RGB
를 사용하여 CSC
를 처리하고 있다면(물론 이 시점에 그런 레거시 코드를 발견한다면 도망치는게 좀 더 좋은 선택일 수 있겠지만), LIBYUV
를 사용하여 64bits
를 지원하도록 수정할 수 있다. 적용은 크게 어렵지 않다.
Getting Start
Getting Start 페이지를 참조하여 쉽게 적용할 수 있다. Getting Start에는 Pre-requisites라는 항목을 통해 depot-tools을 설치하는 과정과, depot-tools에 포함된 gclient
를 사용하여 코드를 다운로드 및 빌드하는 내용이 포함되어있다. 여기에 낚여서(?) 소스코드를 다운받고, 리눅스 혹은 윈도우에서 안드로이드 용으로 크로스 컴파일을 시도하다보면 높은 확률로 머리아픈 상황에 처하게 된다. 보통의 경우에는 소스코드를 보고 눈치를 채겠지만, 나의 경우에는 3일간 어떻게건 컴파일을 해야겠다며 윈도우와 개발서버를 오가며 씨름을 해야했다. 이 글을 남기게 된 이유도 나중에 똑같은 상황이 왔을 때, 참조하기 위함이다. ' ㅅ';
depot-tools
를 설치해서 환경변수를 구성한 뒤, gclient
를 이용하여 소스코드를 받아도 되지만, To get just the source (not buildable): git clone [https://chromium.googlesource.com/libyuv/libyuv](https://chromium.googlesource.com/libyuv/libyuv)
항목처럼 git
을 통해 소스코드를 받아도 된다. not buildable
이라고 적혀있는데, Android.mk
를 사용하여 Shared Library
를 빌드하는데는 전혀 문제가 없다. 오히려 gclient
를 사용하여 sync
를 하게되면 시간이 오래걸리니, git
을 통해 소스코드만 받는게 나을수도 있다. - ㅅ-)
Shared Library Build
gclient
를 사용해서 받았다면 src
디렉터리를, git
을 사용해서 받았다면 libyuv
혹은 체크아웃 받은 경로 내의 Android.mk
파일을 열어보자. NDK
에서 libyuv를 사용하기 위해서 libyuv_static
을 빌드하고, 최종적으로는 libyuv_unittest
를 빌드하는 내용이 들어있다. 다만 NDK
를 사용하여 결과물을 Java
/Kotlin
으로 가져오기 위해서는 Static
이 아닌 Shared
여야 하는 모양이다. .a
파일을 사용하게 될 경우 에러가 발생하므로, BUILD_STATIC_LIBRARY
라고 되어있는 라인을 BUILD_SHARED_LIBRARY
로 변경하도록 하자.
libyuv/source
에 위치한 .cc
파일들과 libyuv/include
에 위치한 .h
파일들을 앱의 JNI
경로에 넣어준 뒤, Android.mk
에 기재된 파일 경로를 재조정해주자. 빌드해보면 LOCAL_MODULE
에 지정해준 값으로 .so
파일이 생성된 것을 볼 수 있다. 이제, JNI
에서 libyuv
를 사용할 수 있다.
include $(CLEAR_VARS)
LOCAL_CPP_EXTENSION := .cc
LOCAL_SRC_FILES := \
source/compare.cc \
source/compare_common.cc \
source/compare_gcc.cc \
source/compare_mmi.cc \
source/compare_msa.cc \
source/compare_neon.cc \
source/compare_neon64.cc \
source/convert.cc \
source/convert_argb.cc \
source/convert_from.cc \
source/convert_from_argb.cc \
source/convert_to_argb.cc \
source/convert_to_i420.cc \
source/cpu_id.cc \
source/planar_functions.cc \
source/rotate.cc \
source/rotate_any.cc \
source/rotate_argb.cc \
source/rotate_common.cc \
source/rotate_gcc.cc \
source/rotate_mmi.cc \
source/rotate_msa.cc \
source/rotate_neon.cc \
source/rotate_neon64.cc \
source/row_any.cc \
source/row_common.cc \
source/row_gcc.cc \
source/row_mmi.cc \
source/row_msa.cc \
source/row_neon.cc \
source/row_neon64.cc \
source/scale.cc \
source/scale_any.cc \
source/scale_argb.cc \
source/scale_common.cc \
source/scale_gcc.cc \
source/scale_mmi.cc \
source/scale_msa.cc \
source/scale_neon.cc \
source/scale_neon64.cc \
source/video_common.cc
common_CFLAGS := -Wall -fexceptions
ifneq ($(LIBYUV_DISABLE_JPEG), "yes")
LOCAL_SRC_FILES += \
source/convert_jpeg.cc \
source/mjpeg_decoder.cc \
source/mjpeg_validate.cc
common_CFLAGS += -DHAVE_JPEG
LOCAL_SHARED_LIBRARIES := libjpeg
endif
LOCAL_CFLAGS += $(common_CFLAGS)
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
LOCAL_MODULE := libyuv_shared
LOCAL_MODULE_TAGS := optional
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_WHOLE_SHARED_LIBRARIES := libyuv_shared
LOCAL_MODULE := libyuv
ifneq ($(LIBYUV_DISABLE_JPEG), "yes")
LOCAL_SHARED_LIBRARIES := libjpeg
endif
include $(BUILD_SHARED_LIBRARY)
마무리
이번에는 libyuv
라이브러리를 사용하여, NDK
에서 CSC
를 할 시 하드웨어 가속을 적용하는 방법에 대해 정리해봤다. 또한 크로미움에 올라온 문서를 참조할 시 나처럼 시행착오를 겪을 가능성이 있어보이는데, 참조할만한 문서가 없어서 더 오래 걸리지 않았나 싶었다. 모쪼록 libyuv
를 적용하는 데 도움이 되기를 바라며, 글을 마무리해본다. ' ㅅ')/
'Programming > Android' 카테고리의 다른 글
NDK 설정과 NDK 버전에 관련된 오류 대처 방법 (0) | 2020.12.08 |
---|---|
Android의 WebView를 사용할 때, 클라이언트 에러를 추적하는 방법 정리. (0) | 2020.03.02 |
Retrofit2를 사용하여 서버의 Digest 인증을 처리하기 (0) | 2019.11.19 |
CLEARTEXT communication to [TARGET_ADDRESS] not permitted by network security policy (0) | 2019.11.08 |
Retrofit2를 이용해서 서버로부터 Content-type이 image/jpeg로 이미지를 받아 Bitmap 객체로 처리하기 (0) | 2019.11.05 |