차량 카메라 HAL

Android에는 Android 부팅 프로세스 초기에 이미지 캡처 및 표시를 허용하고 시스템 수명 기간 동안 계속해서 작동하는 automotive HIDL 하드웨어 추상화 계층(HAL)이 포함되어 있습니다. HAL은 EVS(Exterior View System) 스택을 포함하며, 일반적으로 Android 기반 차량용 인포테인먼트(IVI) 시스템이 장착된 차량의 후방 카메라 및 서라운드 뷰 디스플레이를 지원하는 데 사용됩니다. EVS를 사용하여 사용자 앱에 고급 기능을 구현할 수도 있습니다.

또한, Android에는 EVS 전용 캡처 및 디스플레이 드라이버 인터페이스(/hardware/interfaces/automotive/evs/1.0)도 포함되어 있습니다. 기존 Android 카메라 및 디스플레이 서비스를 기반으로 후방 카메라 앱을 빌드할 수도 있지만, 이러한 앱은 Android 부팅 프로세스에서 너무 늦게 실행될 수 있습니다. 전용 HAL을 사용하면 인터페이스가 간소해지고 EVS 스택을 지원하기 위해 OEM에서 구현해야 하는 작업이 명확해집니다.

시스템 구성요소

EVS에는 다음과 같은 시스템 구성요소가 포함되어 있습니다.

EVS 시스템 구성요소 다이어그램
그림 1. EVS 시스템 구성요소 개요

EVS 앱

샘플 C++ EVS 앱(/packages/services/Car/evs/app)은 참조 구현으로 사용합니다. 이 앱은 EVS Manager에 동영상 프레임을 요청하고, 완료된 프레임을 표시하기 위해 EVS Manager로 다시 전송합니다. 이 앱은 EVS 및 자동차 서비스를 사용할 수 있게 되면 바로 init에서 시작해야 하는데, 목표는 전원이 켜진 후 2초 이내입니다. OEM은 EVS 앱을 원하는 대로 수정하거나 대체할 수 있습니다.

EVS Manager

EVS Manager(/packages/services/Car/evs/manager)는 간단한 후방 카메라 디스플레이에서부터 6DOF 멀티 카메라 렌더링까지 EVS 앱에서 구현하는 데 필요한 구성요소를 제공합니다. EVS Manager의 인터페이스는 HIDL을 통해 제공되며 여러 동시 클라이언트를 허용하도록 빌드됩니다. 다른 앱 및 서비스(특히 자동차 서비스)는 EVS Manager 상태를 쿼리하여 EVS 시스템이 활성 상태인지 알아낼 수 있습니다.

EVS HIDL 인터페이스

카메라와 디스플레이 요소로 구성된 EVS 시스템은 android.hardware.automotive.evs 패키지에 정의되어 있습니다. 이 인터페이스를 실행하는 샘플 구현(합성 테스트 이미지를 생성하고 이미지가 전송된 후 다시 돌아오는지 확인)은 /hardware/interfaces/automotive/evs/1.0/default에서 제공합니다.

OEM은 /hardware/interfaces/automotive/evs의 .hal 파일로 표현되는 API를 구현해야 합니다. 이러한 구현은 실제 카메라에서 데이터를 구성 및 수집하고 Gralloc이 인식할 수 있는 공유 메모리 버퍼를 통해 전송하는 역할을 합니다. 구현의 디스플레이 측은 (일반적으로 EGL 렌더링을 통해) 앱이 채울 수 있는 공유 메모리 버퍼를 제공하고, 실제 디스플레이에 표시하려고 하는 다른 프레임보다 우선하여 완료된 프레임을 표시합니다. EVS 인터페이스의 공급업체 구현은 /vendor/… /device/… 또는 hardware/…에 저장될 수 있습니다(예: /hardware/[vendor]/[platform]/evs).

커널 드라이버

EVS 스택을 지원하는 기기에는 커널 드라이버가 필요합니다. OEM은 새로운 드라이버를 만드는 대신 기존 카메라 또는 디스플레이 하드웨어 드라이버를 통해 EVS 필수 기능을 지원할 수 있습니다. 드라이버 재사용은, 특히 이미지 프레젠테이션에 다른 활성 스레드와의 조정이 필요한 디스플레이 드라이버의 경우 유용할 수 있습니다. Android 8.0에는 v4l2 지원 커널과 출력 이미지 표시를 위한 SurfaceFlinger에 종속되는 v4l2 기반 샘플 드라이버(packages/services/Car/evs/sampleDriver)가 포함되어 있습니다.

EVS 하드웨어 인터페이스 설명

이 섹션에서는 HAL에 관해 설명합니다. 공급업체는 자체 하드웨어에 맞게 이 API를 구현해야 합니다.

IEvsEnumerator

이 객체는 시스템에서 사용할 수 있는 EVS 하드웨어를 나열합니다(한 대 이상의 카메라 및 단일 디스플레이 기기).

getCameraList() generates (vec<CameraDesc> cameras);

시스템의 모든 카메라에 관한 설명이 포함된 벡터를 반환합니다. 카메라 여러 대가 고정되어 있고 부팅 시 인식된다고 가정합니다. 카메라에 관한 자세한 설명은 CameraDesc를 참고하세요.

openCamera(string camera_id) generates (IEvsCamera camera);

고유한 camera_id 문자열로 식별되는 특정 카메라와 상호작용하는 데 사용되는 인터페이스 객체를 가져옵니다. 실패 시 NULL을 반환합니다. 이미 열린 카메라를 다시 열려는 시도는 실패하지 않습니다. 애플리케이션 시작 및 종료와 관련된 경합 상태를 피하려면 카메라를 다시 열어 이전 인스턴스를 종료해야 합니다. 그래야 새로운 요청을 처리할 수 있습니다. 이러한 방식으로 선점된 카메라 인스턴스는 비활성 상태가 되어 최종 폐기를 기다리면서 카메라 상태에 영향을 주는 모든 요청에 대해 OWNERSHIP_LOST 반환 코드로 응답해야 합니다.

closeCamera(IEvsCamera camera);

IEvsCamera 인터페이스를 해제합니다(openCamera() 호출과 반대). closeCamera를 호출하기 전에 stopVideoStream()을 호출하여 카메라 동영상 스트림을 중지해야 합니다.

openDisplay() generates (IEvsDisplay display);

시스템의 EVS 디스플레이와 배타적으로 상호작용하는 데 사용할 인터페이스 객체를 가져옵니다. 한 번에 하나의 클라이언트만 IEvsDisplay의 기능 인스턴스를 보유할 수 있습니다. openCamera에서 설명한 적극적인 열기 동작과 마찬가지로, 언제든지 새 IEvsDisplay 객체를 만들 수 있으며 이 객체는 이전 인스턴스를 사용 중지합니다. 무효화된 인스턴스는 계속해서 존재하고 소유자의 함수 호출에 응답하지만, 인스턴스가 종료될 때 변형 작업을 실행하면 안 됩니다. 최종적으로 클라이언트 앱에서 OWNERSHIP_LOST 오류 반환 코드를 확인하고 비활성 상태의 인터페이스를 종료 및 해제해야 합니다.

closeDisplay(IEvsDisplay display);

IEvsDisplay 인터페이스를 해제합니다(openDisplay() 호출과 반대). getTargetBuffer() 호출을 통해 수신된 미처리 버퍼는 디스플레이를 종료하기 전에 디스플레이로 반환되어야 합니다.

getDisplayState() generates (DisplayState state);

현재 디스플레이 상태를 가져옵니다. HAL 구현은 실제 현재 상태를 보고해야 합니다. 최근에 요청된 상태와 다를 수 있습니다. 디스플레이 상태 변경을 담당하는 로직은 기기 레이어 위에 있어야 합니다. 이에 따라 HAL 구현에서 디스플레이 상태를 자체적으로 변경하는 것이 바람직하지 않게 됩니다. 현재 openDisplay를 호출하여 디스플레이를 보유하고 있는 클라이언트가 없는 경우 이 함수는 NOT_OPEN을 반환합니다. 디스플레이를 보유한 클라이언트가 있다면 EVS 디스플레이의 현재 상태를 보고합니다(IEvsDisplay API 참고).

struct CameraDesc {
    string      camera_id;
    int32       vendor_flags;       // Opaque value
}
  • camera_id: 지정된 카메라를 고유하게 식별하는 문자열입니다. 기기의 커널 기기 이름 또는 기기의 이름(예: rearview)이 될 수 있습니다. 이 문자열의 값은 HAL 구현에서 선택하며 위의 스택에서 불투명하게 사용합니다.
  • vendor_flags: 특수 카메라 정보를 드라이버에서 맞춤 EVS 앱으로 불투명하게 전달하는 메서드입니다. 드라이버에서 EVS 앱까지 해석되지 않은 상태로 전달되며 이는 무시해도 됩니다.

IEvsCamera

이 객체는 단일 카메라를 나타내며 이미지 캡처용 기본 인터페이스입니다.

getCameraInfo() generates (CameraDesc info);

이 카메라의 CameraDesc를 반환합니다.

setMaxFramesInFlight(int32 bufferCount) generates (EvsResult result);

카메라가 지원 요청을 받은 버퍼 체인의 깊이를 지정합니다. IEvsCamera의 클라이언트는 최대 이 프레임 개수까지 동시에 보유할 수 있습니다. doneWithFrame에 의해 반환되지 않고 이와 같이 많은 수의 프레임이 수신기에 전달되면 버퍼가 재사용을 위해 반환될 때까지 스트림이 프레임을 건너뜁니다. 스트림이 이미 실행 중인 경우에도 이 호출은 언제든지 발생할 수 있으며, 이러한 경우에도 버퍼는 적절하게 추가되거나 체인에서 삭제되어야 합니다. 이 진입점에 호출이 이뤄지지 않은 경우 IEvsCamera는 기본적으로 하나 이상의 프레임을 지원하며 더 많은 프레임도 허용합니다.

요청된 bufferCount를 수용할 수 없다면 함수는 BUFFER_NOT_AVAILABLE 또는 기타 관련 오류 코드를 반환합니다. 이 경우 시스템은 이전에 설정된 값으로 계속 작동합니다.

startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);

이 카메라의 EVS 카메라 프레임 전송을 요청합니다. IEvsCameraStream은 stopVideoStream()이 호출될 때까지 새 이미지 프레임이 포함된 주기적 호출을 받기 시작합니다. 프레임은 startVideoStream 호출 후 500밀리초 이내에 전송이 시작되어야 하고 시작된 다음에는 최소 10FPS에서 생성되어야 합니다. 동영상 스트림 시작에 필요한 시간은 후방 카메라 시작 시간 요구사항을 기준으로 계산됩니다. 스트림이 시작되지 않으면 오류 코드가 반환되어야 하며, 시작되면 OK가 반환됩니다.

oneway doneWithFrame(BufferDesc buffer);

IEvsCameraStream에 전달된 프레임을 반환합니다. IEvsCameraStream 인터페이스에 전달된 프레임 사용이 완료되면 재사용할 수 있도록 프레임을 IEvsCamera에 반환해야 합니다. 사용할 수 있는 버퍼 수는 (하나일 수도 있을 만큼) 적고 한정적입니다. 또한, 공급할 프레임이 소진되면 버퍼가 반환될 때까지 프레임이 전송되지 않으며 건너뛴 프레임이 생길 수 있습니다(핸들이 null인 버퍼는 스트림의 끝을 나타내며 이 함수를 통해 반환할 필요가 없음). 성공 시 OK를 반환하고 그렇지 않으면 INVALID_ARG 또는 BUFFER_NOT_AVAILABLE을 비롯한 적절한 오류 코드를 반환합니다.

stopVideoStream();

EVS 카메라 프레임 전송을 중지합니다. 이 전송은 비동기식이므로 호출이 반환된 후 일정 시간 동안 프레임이 계속 도착할 수 있습니다. 스트림 종료 신호가 IEvsCameraStream에 전달되기 전에 각 프레임이 반환되어야 합니다. 이미 중지되었거나 시작된 적이 없는 스트림에 stopVideoStream을 호출해도 되며, 이 경우 호출이 무시됩니다.

getExtendedInfo(int32 opaqueIdentifier) generates (int32 value);

HAL 구현에서 드라이버 관련 정보를 요청합니다. opaqueIdentifier에 허용되는 값은 드라이버와 관련이 있지만, 전달된 값이 없으면 드라이버가 다운될 수 있습니다. 드라이버는 인식되지 않은 opaqueIdentifier에 0을 반환해야 합니다.

setExtendedInfo(int32 opaqueIdentifier, int32 opaqueValue) generates (EvsResult result);

HAL 구현에 드라이버 관련 값을 전송합니다. 이 확장된 정보는 차량별 확장을 지원하기 위한 목적으로만 제공되므로 기본 상태에서 HAL 구현이 이 함수를 호출할 필요는 없습니다. 드라이버에서 값을 인식하고 승인하면 OK가 반환되어야 하며, 그러지 않으면 INVALID_ARG 또는 기타 대표 오류 코드가 반환되어야 합니다.

struct BufferDesc {
    uint32  width;      // Units of pixels
    uint32  height;     // Units of pixels
    uint32  stride;     // Units of pixels
    uint32  pixelSize;  // Size of single pixel in bytes
    uint32  format;     // May contain values from android_pixel_format_t
    uint32  usage;      // May contain values from Gralloc.h
    uint32  bufferId;   // Opaque value
    handle  memHandle;  // gralloc memory buffer handle
}

API를 통해 전달된 이미지를 설명합니다. HAL 드라이브는 이미지 버퍼를 설명하기 위해 이 구조체를 채우며 HAL 클라이언트는 이 구조체를 읽기 전용으로 취급합니다. eglCreateImageKHR() 확장을 통해 EGL로 이미지를 사용하는 데 필요할 수 있으므로 필드에는 클라이언트가 ANativeWindowBuffer 객체를 다시 생성할 수 있도록 충분한 정보가 포함됩니다.

  • width: 제공된 이미지의 픽셀 단위 너비입니다.
  • height: 제공된 이미지의 픽셀 단위 높이입니다.
  • stride: 각 행이 메모리에서 실제로 차지하는 픽셀 수이며, 행 정렬을 위한 패딩을 고려합니다. 버퍼 설명에 관해 gralloc에서 채택한 규칙에 맞게 픽셀로 표현됩니다.
  • pixelSize: 개별 픽셀이 점유한 바이트 수로, 이미지의 행 사이에 삽입할 크기를 바이트로 계산할 수 있도록 합니다(stride 바이트 = stride 픽셀 * pixelSize).
  • format: 이미지에서 사용하는 픽셀 형식입니다. 제공되는 형식은 플랫폼의 OpenGL 구현과 호환되어야 합니다. 호환성 테스트에 통과하려면 카메라 사용에 HAL_PIXEL_FORMAT_YCRCB_420_SP를 사용하는 것이 좋으며, 디스플레이에는 RGBA 또는 BGRA를 사용하는 것이 좋습니다.
  • usage: HAL 구현에서 설정하는 사용 플래그입니다. HAL 클라이언트는 이러한 플래그를 수정하지 않은 상태로 전달해야 합니다. 자세한 내용은 Gralloc.h 관련 플래그를 참고하세요.
  • bufferId: HAL API를 통한 왕복 이동 후에 버퍼를 인식할 수 있도록 HAL 구현에서 지정한 고유한 값입니다. 이 필드에 저장된 값은 HAL 구현에서 임의로 선택할 수 있습니다.
  • memHandle: 이미지 데이터가 포함된 기본 메모리 버퍼의 핸들입니다. 여기에 Gralloc 버퍼 핸들을 저장하도록 HAL 구현에서 선택할 수 있습니다.

IEvsCameraStream

클라이언트에서 비동기 동영상 프레임 전송을 수신하기 위해 이 인터페이스를 구현합니다.

deliverFrame(BufferDesc buffer);

동영상 프레임을 검사할 준비가 될 때마다 HAL에서 호출을 받습니다. 이 메서드에서 수신한 버퍼 핸들은 IEvsCamera::doneWithFrame() 호출을 통해 반환해야 합니다. IEvsCamera::stopVideoStream() 호출을 통해 동영상 스트림이 중지되면 파이프라인이 소모될 때 이 콜백이 계속 실행될 수 있습니다. 각 프레임은 계속 반환되어야 합니다. 스트림의 마지막 프레임이 전송되면 NULL bufferHandle이 전송되어 스트림의 끝을 나타내고 더 이상 프레임이 전송되지 않습니다. NULL bufferHandle 자체는 doneWithFrame()을 통해 다시 전송될 필요가 없지만 다른 핸들은 모두 반환되어야 합니다.

기술적으로 고유한 버퍼 형식이 가능하지만, 호환성 테스트에서 테스트하는 버퍼는 다음 4가지 지원 형식 중 하나여야 합니다. NV21(YCrCb 4:2:0 2차원 평면), YV12(YCrCb 4:2:0 평면), YUYV(YCrCb 4:2:2 인터리브), RGBA(32비트 R:G:B:x), BGRA(32비트 B:G:R:x) 선택한 형식은 플랫폼의 GLES 구현에서 유효한 GL 텍스처 소스여야 합니다.

앱은 BufferDesc 구조체의 bufferId 필드와 memHandle 사이의 어떠한 대응도 의존해서는 안 됩니다. bufferId 값은 본질적으로 HAL 드라이버 구현 전용이기 때문에 HAL 드라이버 구현 시 적합하다고 판단되면 이러한 값을 사용(및 재사용)할 수 있습니다.

IEvsDisplay

이 객체는 Evs 디스플레이를 나타내며, 디스플레이 상태를 제어하고, 실제 이미지 표시를 처리합니다.

getDisplayInfo() generates (DisplayDesc info);

시스템에서 제공하는 EVS 디스플레이에 관한 기본 정보를 반환합니다(DisplayDesc 참고).

setDisplayState(DisplayState state) generates (EvsResult result);

디스플레이 상태를 설정합니다. 클라이언트는 원하는 상태를 표시하도록 디스플레이 상태를 설정할 수 있으며, HAL 구현은 응답이 요청을 무시할 수 있음에도 불구하고 다른 상태에 있는 동안 모든 상태에 관한 요청을 적절하게 수락해야 합니다.

초기화 시, 디스플레이는 NOT_VISIBLE 상태에서 시작하도록 정의되어 있으며, 이후에 클라이언트는 VISIBLE_ON_NEXT_FRAME 상태를 요청하고 동영상을 제공하기 시작해야 합니다. 더 이상 디스플레이가 필요 없으면 마지막 동영상 프레임을 전달한 후 클라이언트에서 NOT_VISIBLE 상태를 요청해야 합니다.

모든 상태는 언제든지 요청할 수 있습니다. 디스플레이가 VISIBLE_ON_NEXT_FRAME으로 설정된 상태에서 이미 표시되고 있다면 계속 표시되어야 합니다. 요청된 상태가 인식할 수 없는 enum 값이 아니라면 항상 OK를 반환합니다. 이러한 경우, INVALID_ARG가 반환됩니다.

getDisplayState() generates (DisplayState state);

디스플레이 상태를 가져옵니다. HAL 구현은 실제 현재 상태를 보고해야 합니다. 최근에 요청된 상태와 다를 수 있습니다. 디스플레이 상태 변경을 담당하는 로직은 기기 레이어 위에 있어야 합니다. 이에 따라 HAL 구현에서 디스플레이 상태를 자체적으로 변경하는 것이 바람직하지 않게 됩니다.

getTargetBuffer() generates (handle bufferHandle);

디스플레이와 연결된 프레임 버퍼의 핸들을 반환합니다. 이 버퍼는 소프트웨어 또는 GL에서 잠그고 기록할 수 있습니다. 이 버퍼는 디스플레이가 더 이상 표시되지 않더라도 returnTargetBufferForDisplay() 호출을 통해 반환되어야 합니다.

기술적으로 고유한 버퍼 형식이 가능하지만, 호환성 테스트에서 테스트하는 버퍼는 다음 4가지 지원 형식 중 하나여야 합니다. NV21(YCrCb 4:2:0 2차원 평면), YV12(YCrCb 4:2:0 평면), YUYV(YCrCb 4:2:2 인터리브), RGBA(32비트 R:G:B:x), BGRA(32비트 B:G:R:x) 선택한 형식은 플랫폼의 GLES 구현에서 유효한 GL 렌더링 타겟이어야 합니다.

오류가 발생하면 null 핸들이 있는 버퍼가 반환되지만, 이러한 버퍼는 returnTargetBufferForDisplay로 다시 전달될 필요가 없습니다.

returnTargetBufferForDisplay(handle bufferHandle) generates (EvsResult result);

버퍼가 표시될 준비가 되었음을 디스플레이에 알립니다. getTargetBuffer() 호출을 통해 가져온 버퍼만 이 호출과 함께 사용할 수 있으며 BufferDesc의 내용은 클라이언트 앱에서 수정할 수 없습니다. 이 호출 후 버퍼는 클라이언트에서 더 이상 사용할 수 없습니다. 성공 시 OK를 반환하고 그렇지 않으면 INVALID_ARG 또는 BUFFER_NOT_AVAILABLE을 비롯한 적절한 오류 코드를 반환합니다.

struct DisplayDesc {
    string  display_id;
    int32   vendor_flags;  // Opaque value
}

EVS 구현에 필요한 EVS 디스플레이 기본 속성을 설명합니다. HAL은 EVS 디스플레이를 설명하기 위해 이 구조체를 채웁니다. 디스플레이는 다른 프레젠테이션 기기와 오버레이되거나 혼합되는 실제 디스플레이 또는 가상 디스플레이가 될 수 있습니다.

  • display_id: 디스플레이를 고유하게 식별하는 문자열로, 기기의 커널 기기 이름 또는 기기 이름(예: rearview)이 될 수 있습니다. 이 문자열의 값은 HAL 구현에서 선택하며 위의 스택에서 불투명하게 사용합니다.
  • vendor_flags: 특수 카메라 정보를 드라이버에서 맞춤 EVS 앱으로 불투명하게 전달하는 메서드입니다. 드라이버에서 EVS 앱까지 해석되지 않은 상태로 전달되며 이는 무시해도 됩니다.
enum DisplayState : uint32 {
    NOT_OPEN,               // Display has not been “opened” yet
    NOT_VISIBLE,            // Display is inhibited
    VISIBLE_ON_NEXT_FRAME,  // Will become visible with next frame
    VISIBLE,                // Display is currently active
    DEAD,                   // Display is not available. Interface should be closed
}

EVS 디스플레이의 상태를 설명하며, 상태는 사용 안함(드라이버에서 보이지 않음) 또는 사용(드라이버에 이미지 표시)일 수 있습니다. 여기에는 디스플레이가 아직 표시되지 않지만 returnTargetBufferForDisplay() 호출을 통해 이미지의 다음 프레임 전송 시 표시되도록 준비된 일시적인 상태가 포함됩니다.

EVS Manager

EVS Manager는 외부 카메라 뷰를 수집 및 표시하기 위해 EVS 시스템에 공개 인터페이스를 제공합니다. 하드웨어 드라이버가 리소스(카메라 또는 디스플레이)당 하나의 활성 인터페이스만 허용하는 경우 EVS Manager는 카메라 액세스를 쉽게 공유할 수 있게 합니다. 단일 기본 EVS 앱은 EVS Manager의 첫 번째 클라이언트이며, 디스플레이 데이터를 쓸 수 있는 유일한 클라이언트입니다. 추가 클라이언트에는 카메라 이미지 읽기 전용 액세스가 부여됩니다.

EVS Manager는 기본 HAL 드라이버와 동일한 API를 구현하며, 여러 동시 클라이언트를 지원하여 확장된 서비스를 제공합니다(둘 이상의 클라이언트에서 EVS Manager를 통해 카메라 하나를 열어 동영상 스트림을 수신할 수 있음).

EVS Manager 및 EVS 하드웨어 API 다이어그램
그림 2. 기본 EVS 하드웨어 API를 미러링하는 EVS Manager

앱 입장에서는 EVS Manager API가 동시 카메라 스트림 액세스를 허용한다는 점을 제외하고 EVS 하드웨어 HAL 구현을 통한 작동이나 EVS Manager API를 통한 작동 시 차이가 없습니다. EVS Manager는 그 자체로 EVS 하드웨어 HAL 레이어의 허용된 유일한 클라이언트로, EVS 하드웨어 HAL의 프록시 역할을 합니다.

다음 섹션에서는 EVS Manager 구현에서 (확장된) 다른 동작이 있는 호출만 설명하며, 나머지 호출은 EVS HAL 설명과 동일합니다.

IEvsEnumerator

openCamera(string camera_id) generates (IEvsCamera camera);

고유한 camera_id 문자열로 식별되는 특정 카메라와 상호작용하는 데 사용되는 인터페이스 객체를 가져옵니다. 실패 시 NULL을 반환합니다. EVS Manager 레이어에서 충분한 시스템 리소스를 사용할 수 있는 경우 이미 열려 있는 카메라를 다른 프로세스에서 다시 열어 여러 소비자 앱에 동영상 스트림을 티잉할 수 있습니다. EVS Manager 레이어의 camera_id 문자열은 EVS 하드웨어 레이어에 보고된 문자열과 동일합니다.

IEvsCamera

EVS Manager 제공 IEvsCamera 구현은 내부적으로 가상화되어 있어 한 클라이언트의 카메라 작업이 다른 클라이언트에게 영향을 미치지 않으므로 카메라에 관한 독립적인 액세스가 유지됩니다.

startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);

동영상 스트림을 시작합니다. 클라이언트는 동일한 기본 카메라에서 독립적으로 동영상 스트림을 시작하고 중지할 수 있습니다. 기본 카메라는 첫 번째 클라이언트가 시작될 때 시작됩니다.

doneWithFrame(uint32 frameId, handle bufferHandle) generates (EvsResult result);

프레임을 반환합니다. 각 클라이언트는 프레임이 완료되면 프레임을 반환해야 하지만, 원하는 기간만큼 프레임을 유지할 수는 있습니다. 클라이언트가 보유한 프레임 수가 구성된 제한에 도달하면 클라이언트가 프레임을 반환할 때까지 더 이상 프레임을 수신하지 않습니다. 이러한 프레임 건너뛰기는 다른 클라이언트에 영향을 미치지 않으므로 정상적으로 모든 프레임을 계속 수신합니다.

stopVideoStream();

동영상 스트림을 중지합니다. 각 클라이언트는 다른 클라이언트에 영향을 미치지 않고 언제든지 동영상 스트림을 중지할 수 있습니다. 특정 카메라의 마지막 클라이언트가 스트림을 중지하면 하드웨어 레이어의 기본 카메라 스트림이 중지됩니다.

setExtendedInfo(int32 opaqueIdentifier, int32 opaqueValue) generates (EvsResult result);

드라이버 특정 값을 전송하여 한 클라이언트가 다른 클라이언트에 영향을 미치도록 합니다. EVS Manager는 공급업체에서 정의한 제어 단어의 의미를 이해할 수 없으므로 제어 단어가 가상화되지 않으며 특정 카메라의 모든 클라이언트에 부작용이 적용됩니다. 예를 들어 공급업체가 이 호출을 사용하여 프레임 속도를 변경했다면 영향을 받는 하드웨어 레이어 카메라의 모든 클라이언트가 새로운 속도로 프레임을 수신합니다.

IEvsDisplay

EVS Manager 수준에서는 하나의 디스플레이 소유자만 허용됩니다. Manager는 기능을 추가하지 않고 단순히 IEvsDisplay 인터페이스를 기본 HAL 구현에 직접 전달합니다.

EVS 앱

Android에는 EVS Manager 및 차량 HAL과 통신하여 기본 후방 카메라 기능을 제공하는 EVS 앱의 네이티브 C++ 참조 구현이 포함되어 있습니다. 이 앱은 시스템 부팅 프로세스 맨 처음에 시작되어야 하며, 사용 가능한 카메라와 자동차 상태(변속 및 좌/우회전 신호 상태)에 따라 적절한 동영상을 표시합니다. OEM은 EVS 앱을 수정하거나 자체 차량별 로직 및 프레젠테이션으로 대체할 수 있습니다.

그림 3. EVS 앱 샘플 로직, 카메라 목록 가져오기


그림 4. EVS 앱 샘플 로직, 프레임 콜백 수신

이미지 데이터는 표준 그래픽 버퍼에서 앱에 제공되므로 이 앱은 소스 버퍼에서 출력 버퍼로 이미지를 이동합니다. 데이터 복사 비용이 발생하지만, 앱에서 원하는 방식으로 이미지를 디스플레이 버퍼에 렌더링할 수도 있습니다.

예를 들어 앱은 인라인 확장 또는 회전 작업을 통해 픽셀 데이터 자체를 이동하도록 선택할 수 있습니다. 또한 소스 이미지를 OpenGL 텍스처로 사용하고 아이콘, 가이드라인, 애니메이션과 같은 가상 요소를 비롯하여 출력 버퍼에 복잡한 장면을 렌더링하도록 선택할 수도 있습니다. 더 정교한 앱은 여러 대의 동시 입력 카메라를 선택하여 단일 출력 프레임으로 병합할 수도 있습니다(예: 하향식 가상 서라운드 차량 뷰에 사용하기 위함).

EVS 디스플레이 HAL에서 EGL/SurfaceFlinger 사용

이 섹션에서는 EGL을 사용하여 Android 10에서 EVS 디스플레이 HAL 구현을 렌더링하는 방법을 설명합니다.

EVS HAL 참조 구현은 EGL을 사용하여 화면에서 카메라 미리보기를 렌더링하고 libgui를 사용하여 타겟 EGL 렌더링 노출 영역을 만듭니다. Android 8 이상에서 libguiVNDK-전용으로 분류되며 공급업체 프로세스에서는 사용 불가한 VNDK 라이브러리가 사용할 수 있는 라이브러리 그룹을 의미합니다. HAL 구현은 공급업체 파티션에 있어야 하므로 공급업체는 HAL 구현의 노출 영역을 사용할 수 없습니다.

공급업체 프로세스용 libgui 빌드

libgui를 사용하는 것은 EVS 디스플레이 HAL 구현에서 EGL/SurfaceFlinger를 사용할 수 있는 유일한 옵션입니다. libgui를 구현하는 가장 간단한 방법은 빌드 스크립트에서 추가 빌드 타겟을 사용하여 frameworks/native/libs/gui를 직접 통하는 것입니다. 이 타겟은 다음 두 필드가 추가된 경우를 제외하고 libgui 타겟과 정확히 일치합니다.

  • name
  • vendor_available
cc_library_shared {
    name: "libgui_vendor",
    vendor_available: true,
    vndk: {
        enabled: false,
    },
    double_loadable: true,

defaults: ["libgui_bufferqueue-defaults"],
srcs: [ … // bufferhub is not used when building libgui for vendors target: { vendor: { cflags: [ "-DNO_BUFFERHUB", "-DNO_INPUT", ], …

참고: 공급업체 타겟은 NO_INPUT 매크로를 사용하여 빌드되며, 이 매크로는 parcel 데이터에서 32비트 워드 하나를 제거합니다. SurfaceFlinger는 이 필드가 삭제되었다고 예상하므로, SurfaceFlinger는 parcel 데이터를 파싱하지 못합니다. 이는 fcntl 실패로 관찰됩니다.

W Parcel  : Attempt to read object from Parcel 0x78d9cffad8 at offset 428 that is not in the object list
E Parcel  : fcntl(F_DUPFD_CLOEXEC) failed in Parcel::read, i is 0, fds[i] is 0, fd_count is 20, error: Unknown error 2147483647
W Parcel  : Attempt to read object from Parcel 0x78d9cffad8 at offset 544 that is not in the object list

이 조건을 해결하려면 다음 안내를 따르세요.

diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 6066421fa..25cf5f0ce 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -54,6 +54,9 @@ status_t layer_state_t::write(Parcel& output) const
    output.writeFloat(color.b);
#ifndef NO_INPUT
    inputInfo.write(output);
+#else
+    // Write a dummy 32-bit word.
+    output.writeInt32(0);
#endif
    output.write(transparentRegion);
    output.writeUint32(transform);

아래에 샘플 빌드 안내가 나와 있습니다. $(ANDROID_PRODUCT_OUT)/system/lib64/libgui_vendor.so가 생성될 것으로 예상됩니다.

$ cd <your_android_source_tree_top>
$ . ./build/envsetup.
$ lunch <product_name>-<build_variant>
============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=10
TARGET_PRODUCT=<product_name>
TARGET_BUILD_VARIANT=<build_variant>
TARGET_BUILD_TYPE=release
TARGET_ARCH=arm64
TARGET_ARCH_VARIANT=armv8-a
TARGET_CPU_VARIANT=generic
TARGET_2ND_ARCH=arm
TARGET_2ND_ARCH_VARIANT=armv7-a-neon
TARGET_2ND_CPU_VARIANT=cortex-a9
HOST_ARCH=x86_64
HOST_2ND_ARCH=x86
HOST_OS=linux
HOST_OS_EXTRA=<host_linux_version>
HOST_CROSS_OS=windows
HOST_CROSS_ARCH=x86
HOST_CROSS_2ND_ARCH=x86_64
HOST_BUILD_TYPE=release
BUILD_ID=QT
OUT_DIR=out
============================================

$ m -j libgui_vendor … $ find $ANDROID_PRODUCT_OUT/system -name "libgui_vendor*" .../out/target/product/hawk/system/lib64/libgui_vendor.so .../out/target/product/hawk/system/lib/libgui_vendor.so

EVS HAL 구현에서 바인더 사용

Android 8 이상에서는 /dev/binder 기기 노드가 프레임워크 프로세스에 배타적이므로 공급업체 프로세스에 액세스할 수 없습니다. 대신 공급업체 프로세스는 /dev/hwbinder를 사용해야 하며 모든 AIDL 인터페이스를 HIDL로 변환해야 합니다. 공급업체 프로세스 간에 AIDL 인터페이스를 계속 사용하려면 바인더 도메인인 /dev/vndbinder를 사용합니다.

IPC 도메인 설명
/dev/binder AIDL 인터페이스를 사용하는 프레임워크/앱 프로세스 간 통신
/dev/hwbinder HIDL 인터페이스를 사용하는 프레임워크/공급업체 프로세스 간 통신
HIDL 인터페이스를 사용하는 공급업체 프로세스 간 통신
/dev/vndbinder AIDL 인터페이스를 사용하는 공급업체/공급업체 프로세스 간 통신

SurfaceFlinger는 AIDL 인터페이스를 정의하지만, 공급업체 프로세스는 프레임워크 프로세스와 통신하기 위해 HIDL 인터페이스만 사용할 수 있습니다. 기존 AIDL 인터페이스를 HIDL로 변환하려면 적지 않은 작업이 필요합니다. 하지만 다행히 Android에서는 사용자 공간 라이브러리 프로세스가 연결되는 libbinder용 바인더 드라이버를 선택할 수 있는 메서드를 제공합니다.

diff --git a/evs/sampleDriver/service.cpp b/evs/sampleDriver/service.cpp
index d8fb3166..5fd02935 100644
--- a/evs/sampleDriver/service.cpp
+++ b/evs/sampleDriver/service.cpp
@@ -21,6 +21,7 @@
#include <utils/Errors.h>
#include <utils/StrongPointer.h>
#include <utils/Log.h>
+#include <binder/ProcessState.h>

#include "ServiceNames.h"
#include "EvsEnumerator.h"
@@ -43,6 +44,9 @@ using namespace android;
int main() {
    ALOGI("EVS Hardware Enumerator service is starting");


+    // Use /dev/binder for SurfaceFlinger
+    ProcessState::initWithDriver("/dev/binder");
+


    // Start a thread to listen to video device addition events.
    std::atomic<bool> running { true };
    std::thread ueventHandler(EvsEnumerator::EvsUeventThread, std::ref(running));

참고: 공급업체 프로세스는 Process 또는 IPCThreadState를 호출하기 전에 또는 바인더를 호출하기 전에 이 메서드를 호출해야 합니다.

SELinux 정책

기기 구현이 전체 트레블인 경우 SELinux는 공급업체 프로세스에서 /dev/binder를 사용하지 못하게 합니다. 예를 들어 EVS HAL 샘플 구현은 hal_evs_driver 도메인에 할당되며 binder_device 도메인의 읽기/쓰기 권한이 필요합니다.

W ProcessState: Opening '/dev/binder' failed: Permission denied
F ProcessState: Binder driver could not be opened. Terminating.
F libc    : Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 9145 (android.hardwar), pid 9145 (android.hardwar)
W android.hardwar: type=1400 audit(0.0:974): avc: denied { read write } for name="binder" dev="tmpfs" ino=2208 scontext=u:r:hal_evs_driver:s0 tcontext=u:object_r:binder_device:s0 tclass=chr_file permissive=0

그러나 이러한 권한을 추가하면 전체 트레블 기기에서 system/sepolicy/domain.te 에 정의된 다음 neverallow 규칙을 위반하므로 빌드 실패가 발생합니다.

libsepol.report_failure: neverallow on line 631 of system/sepolicy/public/domain.te (or line 12436 of policy.conf) violated by allow hal_evs_driver binder_device:chr_file { read write };
libsepol.check_assertions: 1 neverallow failures occurred
full_treble_only(`
neverallow {
    domain
    -coredomain
    -appdomain
    -binder_in_vendor_violators
} binder_device:chr_file rw_file_perms;
')

binder_in_vendor_violators는 버그를 포착하고 개발을 안내하기 위해 제공되는 속성입니다. 또한 위에 설명한 Android 10 위반을 해결하는 데 사용할 수도 있습니다.

diff --git a/evs/sepolicy/evs_driver.te b/evs/sepolicy/evs_driver.te
index f1f31e9fc..6ee67d88e 100644
--- a/evs/sepolicy/evs_driver.te
+++ b/evs/sepolicy/evs_driver.te
@@ -3,6 +3,9 @@ type hal_evs_driver, domain, coredomain;
hal_server_domain(hal_evs_driver, hal_evs)
hal_client_domain(hal_evs_driver, hal_evs)

+# Allow to use /dev/binder
+typeattribute hal_evs_driver binder_in_vendor_violators;
+
# allow init to launch processes in this context
type hal_evs_driver_exec, exec_type, file_type, system_file_type;
init_daemon_domain(hal_evs_driver)

EVS HAL 참조 구현을 공급업체 프로세스로 빌드

참고로 packages/services/Car/evs/Android.mk에 아래 변경사항을 적용할 수 있습니다. 설명된 모든 변경사항이 구현에서 작동하는지 확인해야 합니다.

diff --git a/evs/sampleDriver/Android.mk b/evs/sampleDriver/Android.mk
index 734feea7d..0d257214d 100644
--- a/evs/sampleDriver/Android.mk
+++ b/evs/sampleDriver/Android.mk
@@ -16,7 +16,7 @@ LOCAL_SRC_FILES := \
LOCAL_SHARED_LIBRARIES := \
    android.hardware.automotive.evs@1.0 \
    libui \
-    libgui \
+    libgui_vendor \
    libEGL \
    libGLESv2 \
    libbase \
@@ -33,6 +33,7 @@ LOCAL_SHARED_LIBRARIES := \
LOCAL_INIT_RC := android.hardware.automotive.evs@1.0-sample.rc

LOCAL_MODULE := android.hardware.automotive.evs@1.0-sample
+LOCAL_PROPRIETARY_MODULE := true

LOCAL_MODULE_TAGS := optional
LOCAL_STRIP_MODULE := keep_symbols
@@ -40,6 +41,7 @@ LOCAL_STRIP_MODULE := keep_symbols
LOCAL_CFLAGS += -DLOG_TAG=\"EvsSampleDriver\"
LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
+LOCAL_CFLAGS += -Iframeworks/native/include

#NOTE:  It can be helpful, while debugging, to disable optimizations
#LOCAL_CFLAGS += -O0 -g
diff --git a/evs/sampleDriver/service.cpp b/evs/sampleDriver/service.cpp
index d8fb31669..5fd029358 100644
--- a/evs/sampleDriver/service.cpp
+++ b/evs/sampleDriver/service.cpp
@@ -21,6 +21,7 @@
#include <utils/Errors.h>
#include <utils/StrongPointer.h>
#include <utils/Log.h>
+#include <binder/ProcessState.h>

#include "ServiceNames.h"
#include "EvsEnumerator.h"
@@ -43,6 +44,9 @@ using namespace android;
int main() {
    ALOGI("EVS Hardware Enumerator service is starting");
+    // Use /dev/binder for SurfaceFlinger
+    ProcessState::initWithDriver("/dev/binder");
+
     // Start a thread to listen video device addition events.
    std::atomic<bool> running { true };
    std::thread ueventHandler(EvsEnumerator::EvsUeventThread, std::ref(running));
diff --git a/evs/sepolicy/evs_driver.te b/evs/sepolicy/evs_driver.te
index f1f31e9fc..632fc7337 100644
--- a/evs/sepolicy/evs_driver.te
+++ b/evs/sepolicy/evs_driver.te
@@ -3,6 +3,9 @@ type hal_evs_driver, domain, coredomain;
hal_server_domain(hal_evs_driver, hal_evs)
hal_client_domain(hal_evs_driver, hal_evs)

+# allow to use /dev/binder
+typeattribute hal_evs_driver binder_in_vendor_violators;
+
# allow init to launch processes in this context
type hal_evs_driver_exec, exec_type, file_type, system_file_type;
init_daemon_domain(hal_evs_driver)
@@ -22,3 +25,7 @@ allow hal_evs_driver ion_device:chr_file r_file_perms;

# Allow the driver to access kobject uevents
allow hal_evs_driver self:netlink_kobject_uevent_socket create_socket_perms_no_ioctl;
+
+# Allow the driver to use the binder device
+allow hal_evs_driver binder_device:chr_file rw_file_perms;