차량 카메라 HAL

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

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

시스템 구성요소

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

EVS 시스템 구성 요소 다이어그램

그림 1. EVS 시스템 구성 요소 개요.

EVS 앱

샘플 C++ EVS 앱( /packages/services/Car/evs/app )은 참조 구현 역할을 합니다. 이 앱은 EVS Manager에서 비디오 프레임을 요청하고 표시할 완성된 프레임을 EVS Manager에 다시 보내는 역할을 담당합니다. EVS 및 Car Service를 사용할 수 있게 되자마자 init에 의해 시작될 것으로 예상되며, 전원이 켜진 후 2초 이내를 목표로 합니다. OEM은 원하는 대로 EVS 앱을 수정하거나 교체할 수 있습니다.

EVS 관리자

EVS Manager( /packages/services/Car/evs/manager )는 간단한 후방 카메라 디스플레이부터 6DOF 다중 카메라 렌더링까지 모든 것을 구현하기 위해 EVS 앱에 필요한 빌딩 블록을 제공합니다. 해당 인터페이스는 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 앱까지 해석되지 않은 채 전달되며, 이를 무시해도 됩니다.

IEvs카메라

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

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 호출 후 500ms 이내에 전달을 시작해야 하며 시작된 후에는 최소 10FPS로 생성되어야 합니다. 비디오 스트림을 시작하는 데 필요한 시간은 후방 카메라 시작 시간 요구 사항에 효과적으로 반영됩니다. 스트림이 시작되지 않으면 오류 코드가 반환되어야 합니다. 그렇지 않으면 OK가 반환됩니다.

oneway doneWithFrame(BufferDesc buffer);

IEvsCameraStream으로 전달된 프레임을 반환합니다. IEvsCameraStream 인터페이스에 전달된 프레임 사용이 완료되면 재사용을 위해 프레임을 IEvsCamera에 반환해야 합니다. 작고 유한한 수의 버퍼를 사용할 수 있으며(아마도 1개만큼 작을 수 있음) 공급이 모두 소진되면 버퍼가 반환될 때까지 더 이상 프레임이 전달되지 않아 잠재적으로 프레임을 건너뛰는 결과가 발생합니다(널 핸들이 있는 버퍼는 끝을 나타냄). 스트림의 것이며 이 함수를 통해 반환될 필요가 없습니다). 성공하면 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 . 이미지 데이터가 포함된 기본 메모리 버퍼에 대한 핸들입니다. HAL 구현은 여기에 Gralloc 버퍼 핸들을 저장하도록 선택할 수 있습니다.

IEvsCameraStream

클라이언트는 비동기 비디오 프레임 전달을 수신하기 위해 이 인터페이스를 구현합니다.

deliverFrame(BufferDesc buffer);

비디오 프레임을 검사할 준비가 될 때마다 HAL로부터 호출을 받습니다. 이 메서드에서 수신한 버퍼 핸들은 IEvsCamera::doneWithFrame() 호출을 통해 반환되어야 합니다. IEvsCamera::stopVideoStream() 호출을 통해 비디오 스트림이 중지되면 파이프라인이 배수될 때 이 콜백이 계속될 수 있습니다. 각 프레임은 여전히 ​​반환되어야 합니다. 스트림의 마지막 프레임이 전달되면 NULL bufferHandle이 전달되어 스트림의 끝을 나타내며 더 이상 프레임 전달이 발생하지 않습니다. NULL bufferHandle 자체는 doneWithFrame() 통해 다시 보낼 필요가 없지만 다른 모든 핸들은 반환되어야 합니다.

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

앱은 bufferId 필드와 BufferDesc 구조의 memHandle 간의 대응 관계에 의존해서는 됩니다. bufferId 값은 기본적으로 HAL 드라이버 구현에만 적용되며 적합하다고 판단되는 대로 사용(및 재사용)할 수 있습니다.

IEvs디스플레이

이 객체는 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 으로 설정하면 계속 표시되어야 합니다. 요청된 상태가 인식할 수 없는 열거형 값이 아닌 한 항상 OK를 반환합니다. 이 경우 INVALID_ARG 가 반환됩니다.

getDisplayState() generates (DisplayState state);

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

getTargetBuffer() generates (handle bufferHandle);

디스플레이와 관련된 프레임 버퍼에 대한 핸들을 반환합니다. 이 버퍼는 소프트웨어 및/또는 GL에 의해 잠겨지고 기록될 수 있습니다. 이 버퍼는 디스플레이가 더 이상 표시되지 않는 경우에도 returnTargetBufferForDisplay() 호출을 통해 반환되어야 합니다.

독점 버퍼 형식은 기술적으로 가능하지만 호환성 테스트를 위해서는 버퍼가 NV21(YCrCb 4:2:0 반평면), 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 관리자

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

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

EVS 관리자 및 EVS 하드웨어 API 다이어그램.

그림 2. EVS Manager는 기본 EVS 하드웨어 API를 미러링합니다.

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 관리자 계층의 camera_id 문자열은 EVS 하드웨어 계층에 보고된 문자열과 동일합니다.

IEvs카메라

IEvsCamera 구현을 제공하는 EVS Manager는 내부적으로 가상화되므로 한 클라이언트의 카메라 작업은 카메라에 대한 독립적인 액세스를 유지하는 다른 클라이언트에 영향을 주지 않습니다.

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는 공급업체가 정의한 제어 단어의 의미를 이해할 수 없기 때문에 가상화되지 않으며 부작용은 해당 카메라의 모든 클라이언트에 적용됩니다. 예를 들어 공급업체가 이 호출을 사용하여 프레임 속도를 변경한 경우 영향을 받는 하드웨어 계층 카메라의 모든 클라이언트는 새 속도로 프레임을 수신합니다.

IEvs디스플레이

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 이상에서는 libgui VNDK-private 으로 분류됩니다. 이는 공급업체 프로세스에서 사용할 수 없는 VNDK 라이브러리에서 사용할 수 있는 라이브러리 그룹을 나타냅니다. HAL 구현은 공급업체 파티션에 상주해야 하므로 공급업체는 HAL 구현에서 Surface를 사용할 수 없습니다.

공급업체 프로세스용 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", ], …

참고: 공급업체 대상은 구획 데이터에서 32비트 단어 하나를 제거하는 NO_INPUT 매크로를 사용하여 구축됩니다. SurfaceFlinger는 이 필드가 제거될 것으로 예상하므로 SurfaceFlinger는 소포를 구문 분석하지 못합니다. 이는 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 인터페이스를 사용한 프레임워크/앱 프로세스 간 IPC
/dev/hwbinder HIDL 인터페이스를 사용하는 프레임워크/공급업체 프로세스 간 IPC
HIDL 인터페이스를 사용하는 공급업체 프로세스 간 IPC
/dev/vndbinder AIDL 인터페이스를 사용하는 공급업체/공급업체 프로세스 간 IPC

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 도메인에 대한 r/w 권한이 필요합니다.

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;