Lớp trừu tượng phần cứng (HAL) cho camera trên xe

Android chứa một Lớp trừu tượng phần cứng HIDL (HAL) dành cho ô tô, giúp chụp và hiển thị hình ảnh ngay từ đầu quy trình khởi động Android và tiếp tục hoạt động trong suốt thời gian hoạt động của hệ thống. HAL bao gồm ngăn xếp hệ thống quan sát bên ngoài (EVS) và thường được dùng để hỗ trợ camera lùi và màn hình quan sát xung quanh trong các xe có hệ thống Thông tin giải trí trên ô tô (IVI) dựa trên Android. EVS cũng cho phép triển khai các tính năng nâng cao trong ứng dụng của người dùng.

Android cũng bao gồm giao diện trình điều khiển chụp và hiển thị dành riêng cho EVS (trong /hardware/interfaces/automotive/evs/1.0). Mặc dù có thể tạo ứng dụng camera chiếu hậu ngoài các dịch vụ màn hình và camera Android hiện có, nhưng một ứng dụng như vậy có thể sẽ chạy quá muộn trong quá trình khởi động Android. Việc sử dụng HAL chuyên dụng cho phép giao diện được tinh giản và giúp nhà sản xuất thiết bị gốc (OEM) biết rõ những gì cần triển khai để hỗ trợ ngăn xếp EVS.

Thành phần hệ thống

EVS bao gồm các thành phần hệ thống sau:

Sơ đồ thành phần hệ thống EVS
Hình 1. Tổng quan về các thành phần hệ thống EVS.

Ứng dụng EVS

Ứng dụng EVS C++ mẫu (/packages/services/Car/evs/app) đóng vai trò là một phương thức triển khai tham chiếu. Ứng dụng này chịu trách nhiệm yêu cầu khung hình video từ Trình quản lý EVS và gửi các khung hình đã hoàn tất để hiển thị lại cho Trình quản lý EVS. Dịch vụ này dự kiến sẽ được khởi động bằng cách khởi tạo ngay khi có EVS và Dịch vụ ô tô, mục tiêu là trong vòng hai (2) giây sau khi bật nguồn. Nhà sản xuất thiết bị gốc (OEM) có thể sửa đổi hoặc thay thế ứng dụng EVS nếu muốn.

Trình quản lý EVS

Trình quản lý EVS (/packages/services/Car/evs/manager) cung cấp các khối xây dựng mà ứng dụng EVS cần để triển khai mọi thứ, từ màn hình camera lùi đơn giản đến kết xuất nhiều camera 6DOF. Giao diện của tính năng này được trình bày thông qua HIDL và được xây dựng để chấp nhận nhiều ứng dụng đồng thời. Các ứng dụng và dịch vụ khác (cụ thể là Dịch vụ ô tô) có thể truy vấn trạng thái của Trình quản lý EVS để tìm hiểu thời điểm hệ thống EVS hoạt động.

Giao diện EVS HIDL

Hệ thống EVS, cả máy ảnh và các phần tử hiển thị, được xác định trong gói android.hardware.automotive.evs. Một cách triển khai mẫu thực thi giao diện (tạo hình ảnh kiểm thử tổng hợp và xác thực hình ảnh thực hiện hành trình khứ hồi) được cung cấp trong /hardware/interfaces/automotive/evs/1.0/default.

Nhà sản xuất thiết bị gốc (OEM) chịu trách nhiệm triển khai API do các tệp .hal biểu thị trong /hardware/interfaces/automotive/evs. Các phương thức triển khai như vậy chịu trách nhiệm định cấu hình và thu thập dữ liệu từ máy ảnh thực tế, đồng thời phân phối dữ liệu đó thông qua vùng đệm bộ nhớ dùng chung mà Gralloc có thể nhận dạng. Phần hiển thị của quá trình triển khai có trách nhiệm cung cấp vùng đệm bộ nhớ dùng chung mà ứng dụng có thể điền sẵn (thường là kết xuất EGL) và hiển thị các khung đã hoàn tất thay vì bất kỳ nội dung nào khác có thể xuất hiện trên màn hình thực. Các hoạt động triển khai giao diện EVS của nhà cung cấp có thể được lưu trữ trong /vendor/… /device/… hoặc hardware/… (ví dụ: /hardware/[vendor]/[platform]/evs).

Trình điều khiển hạt nhân

Thiết bị hỗ trợ ngăn xếp EVS cần có trình điều khiển nhân. Thay vì tạo trình điều khiển mới, OEM có thể chọn hỗ trợ các tính năng bắt buộc của EVS thông qua trình điều khiển phần cứng màn hình hoặc máy ảnh hiện có. Việc sử dụng lại trình điều khiển có thể mang lại lợi ích, đặc biệt là đối với trình điều khiển hiển thị, trong đó việc trình bày hình ảnh có thể yêu cầu phối hợp với các luồng đang hoạt động khác. Android 8.0 bao gồm một trình điều khiển mẫu dựa trên v4l2 (trong packages/services/Car/evs/sampleDriver) phụ thuộc vào hạt nhân để hỗ trợ v4l2 và trên SurfaceFlinger để hiển thị hình ảnh đầu ra.

Nội dung mô tả về giao diện phần cứng EVS

Phần này mô tả HAL. Các nhà cung cấp dự kiến sẽ cung cấp các phương thức triển khai API này được điều chỉnh cho phần cứng của họ.

IEvsEnumerator

Đối tượng này chịu trách nhiệm liệt kê phần cứng EVS có sẵn trong hệ thống (một hoặc nhiều máy ảnh và một thiết bị hiển thị).

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

Trả về một vectơ chứa nội dung mô tả cho tất cả máy ảnh trong hệ thống. Giả định tập hợp camera này là cố định và có thể nhận biết được tại thời điểm khởi động. Để biết thông tin chi tiết về nội dung mô tả máy ảnh, hãy xem CameraDesc.

openCamera(string camera_id) generates (IEvsCamera camera);

Lấy một đối tượng giao diện dùng để tương tác với một máy ảnh cụ thể được xác định bằng chuỗi camera_id duy nhất. Trả về giá trị NULL nếu không thành công. Không thể mở lại một máy ảnh đã mở. Để tránh các điều kiện tương tranh liên quan đến việc khởi động và tắt ứng dụng, việc mở lại máy ảnh sẽ tắt phiên bản trước đó để có thể thực hiện yêu cầu mới. Một thực thể máy ảnh đã được ưu tiên theo cách này phải được đặt ở trạng thái không hoạt động, chờ huỷ bỏ hoàn toàn và phản hồi mọi yêu cầu ảnh hưởng đến trạng thái máy ảnh bằng mã trả về là OWNERSHIP_LOST.

closeCamera(IEvsCamera camera);

Phát hành giao diện IEvsCamera (và ngược lại với lệnh gọi openCamera()). Bạn phải dừng luồng video của máy ảnh bằng cách gọi stopVideoStream() trước khi gọi closeCamera.

openDisplay() generates (IEvsDisplay display);

Lấy một đối tượng giao diện dùng để tương tác riêng với màn hình EVS của hệ thống. Chỉ một ứng dụng có thể lưu giữ một thực thể chức năng của IEvsDisplay tại một thời điểm. Tương tự như hành vi mở mạnh mẽ được mô tả trong openCamera, đối tượng IEvsDisplay mới có thể được tạo bất cứ lúc nào và vô hiệu hoá mọi thực thể trước đó. Các thực thể không hợp lệ vẫn tiếp tục tồn tại và phản hồi các lệnh gọi hàm từ chủ sở hữu nhưng không được thực hiện thao tác thay đổi khi đã chết. Cuối cùng, ứng dụng khách dự kiến sẽ nhận thấy mã trả về lỗi OWNERSHIP_LOST, đồng thời đóng và giải phóng giao diện không hoạt động.

closeDisplay(IEvsDisplay display);

Phát hành giao diện IEvsDisplay (và ngược lại với lệnh gọi openDisplay()). Các vùng đệm chưa xử lý nhận được bằng các lệnh gọi getTargetBuffer() phải được trả về màn hình trước khi đóng màn hình.

getDisplayState() generates (DisplayState state);

Lấy trạng thái hiển thị hiện tại. Việc triển khai HAL sẽ báo cáo trạng thái thực tế hiện tại, có thể khác với trạng thái được yêu cầu gần đây nhất. Logic chịu trách nhiệm thay đổi trạng thái hiển thị phải tồn tại phía trên lớp thiết bị, khiến việc triển khai HAL tự động thay đổi trạng thái hiển thị là không mong muốn. Nếu màn hình hiện không được bất kỳ ứng dụng khách nào giữ (bằng lệnh gọi đến openDisplay), thì hàm này sẽ trả về NOT_OPEN. Nếu không, nó sẽ báo cáo trạng thái hiện tại của Màn hình EVS (xem API IEvsDisplay).

struct CameraDesc {
    string      camera_id;
    int32       vendor_flags;       // Opaque value
}
  • camera_id. Một chuỗi nhận dạng duy nhất một máy ảnh nhất định. Có thể là tên thiết bị hạt nhân của thiết bị hoặc tên của thiết bị, chẳng hạn như rearview (camera lùi). Giá trị cho chuỗi này được quy trình triển khai HAL (Lớp trừu tượng phần cứng) chọn và được ngăn xếp ở trên sử dụng một cách mờ đục.
  • vendor_flags. Một phương thức để truyền thông tin máy ảnh chuyên biệt từ trình điều khiển đến một ứng dụng EVS tuỳ chỉnh. Thông tin này được truyền không được diễn giải từ trình điều khiển đến ứng dụng EVS và ứng dụng này có thể bỏ qua thông tin đó.

IEvsCamera

Đối tượng này đại diện cho một máy ảnh và là giao diện chính để chụp ảnh.

getCameraInfo() generates (CameraDesc info);

Trả về CameraDesc của máy ảnh này.

setMaxFramesInFlight(int32 bufferCount) generates (EvsResult result);

Chỉ định độ sâu của chuỗi vùng đệm mà máy ảnh được yêu cầu hỗ trợ. Ứng dụng của IEvsCamera có thể giữ đồng thời tối đa số lượng khung hình này. Nếu nhiều khung hình này đã được phân phối đến bộ thu mà không được doneWithFrame trả về, thì luồng sẽ bỏ qua các khung hình cho đến khi bộ đệm được trả về để sử dụng lại. Lệnh gọi này có thể đến bất cứ lúc nào, ngay cả khi các luồng đang chạy, trong trường hợp đó, bạn nên thêm hoặc xoá bộ đệm khỏi chuỗi khi thích hợp. Nếu không có lệnh gọi nào được thực hiện đến điểm truy cập này, thì theo mặc định, IEvsCamera sẽ hỗ trợ ít nhất một khung hình; với nhiều khung hình hơn được chấp nhận.

Nếu không thể đáp ứng bufferCount được yêu cầu, hàm sẽ trả về BUFFER_NOT_AVAILABLE hoặc mã lỗi có liên quan khác. Trong trường hợp này, hệ thống sẽ tiếp tục hoạt động với giá trị đã đặt trước đó.

startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);

Yêu cầu phân phối khung hình máy ảnh qua EVS từ máy ảnh này. IEvsCameraStream bắt đầu nhận các lệnh gọi định kỳ với các khung hình ảnh mới cho đến khi stopVideoStream() được gọi. Khung hình phải bắt đầu được phân phối trong vòng 500 mili giây sau lệnh gọi startVideoStream và sau khi bắt đầu, phải được tạo ở tốc độ tối thiểu là 10 khung hình/giây. Thời gian cần thiết để bắt đầu truyền phát video sẽ được tính hiệu quả theo mọi yêu cầu về thời gian khởi động camera lùi. Nếu luồng chưa bắt đầu, thì mã lỗi sẽ được trả về; nếu không, hàm sẽ trả về OK.

oneway doneWithFrame(BufferDesc buffer);

Trả về một khung hình được phân phối đến IEvsCameraStream. Khi bạn đã sử dụng xong một khung được phân phối đến giao diện IEvsCameraStream, bạn phải trả lại khung đó cho IEvsCamera để sử dụng lại. Có một số lượng nhỏ, hữu hạn vùng đệm (có thể chỉ có một vùng đệm) và nếu hết vùng đệm, thì sẽ không có khung nào được phân phối cho đến khi vùng đệm được trả về, điều này có thể dẫn đến việc bỏ qua khung (vùng đệm có tay cầm rỗng biểu thị phần cuối của luồng và không cần được trả về thông qua hàm này). Trả về OK nếu thành công hoặc mã lỗi thích hợp có thể bao gồm INVALID_ARG hoặc BUFFER_NOT_AVAILABLE.

stopVideoStream();

Dừng phân phối khung hình máy ảnh EVS. Vì quá trình phân phối không đồng bộ, nên các khung hình có thể tiếp tục đến trong một khoảng thời gian sau khi lệnh gọi này trả về. Mỗi khung phải được trả về cho đến khi đóng luồng được báo hiệu cho IEvsCameraStream. Bạn có thể gọi stopVideoStream trên một luồng đã dừng hoặc chưa bao giờ bắt đầu, trong trường hợp này, luồng sẽ bị bỏ qua.

getExtendedInfo(int32 opaqueIdentifier) generates (int32 value);

Yêu cầu thông tin dành riêng cho trình điều khiển từ quá trình triển khai HAL. Các giá trị được phép cho opaqueIdentifier là tuỳ theo người lái xe cụ thể, nhưng không giá trị nào được truyền có thể gây sự cố cho trình điều khiển. Trình điều khiển sẽ trả về 0 đối với mọi opaqueIdentifier không nhận dạng được.

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

Gửi giá trị dành riêng cho người lái xe đến quá trình triển khai HAL (Lớp trừu tượng phần cứng). Tiện ích này chỉ được cung cấp để hỗ trợ các tiện ích dành riêng cho xe và không có phương thức triển khai HAL nào yêu cầu lệnh gọi này hoạt động ở trạng thái mặc định. Nếu người lái xe nhận ra và chấp nhận các giá trị, thì hàm sẽ trả về OK; nếu không, hàm sẽ trả về INVALID_ARG hoặc mã lỗi đại diện khác.

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
}

Mô tả một hình ảnh được truyền qua API. Ổ đĩa HAL chịu trách nhiệm điền vào cấu trúc này để mô tả vùng đệm hình ảnh và ứng dụng HAL phải coi cấu trúc này là chỉ có thể đọc. Các trường này chứa đủ thông tin để cho phép ứng dụng khách tái tạo đối tượng ANativeWindowBuffer, vì có thể cần phải sử dụng hình ảnh với EGL bằng tiện ích eglCreateImageKHR().

  • width. Chiều rộng tính bằng pixel của hình ảnh được trình bày.
  • height. Chiều cao tính bằng pixel của hình ảnh được trình bày.
  • stride. Số pixel mà mỗi hàng thực sự chiếm trong bộ nhớ, tính cả khoảng đệm để căn chỉnh các hàng. Được biểu thị bằng pixel để khớp với quy ước mà gralloc áp dụng cho nội dung mô tả vùng đệm.
  • pixelSize. Số byte mà mỗi pixel riêng lẻ chiếm, cho phép tính toán kích thước tính bằng byte cần thiết để di chuyển giữa các hàng trong hình ảnh (stride tính bằng byte = stride tính bằng pixel * pixelSize).
  • format. Định dạng pixel mà hình ảnh sử dụng. Định dạng được cung cấp phải tương thích với cách triển khai OpenGL của nền tảng. Để vượt qua quy trình kiểm thử khả năng tương thích, bạn nên sử dụng HAL_PIXEL_FORMAT_YCRCB_420_SP để sử dụng máy ảnh và RGBA hoặc BGRA để hiển thị.
  • usage. Cờ sử dụng do quá trình triển khai HAL thiết lập. Các ứng dụng HAL dự kiến sẽ truyền các cờ này mà không sửa đổi (để biết thông tin chi tiết, hãy tham khảo các cờ liên quan đến Gralloc.h).
  • bufferId. Một giá trị duy nhất do quá trình triển khai HAL chỉ định để cho phép nhận dạng một vùng đệm sau khi thực hiện một hành trình trọn vẹn thông qua các API HAL. Giá trị được lưu trữ trong trường này có thể được lựa chọn tuỳ ý bởi quá trình triển khai HAL.
  • memHandle. Tay cầm cho vùng đệm bộ nhớ cơ bản chứa dữ liệu hình ảnh. Việc triển khai HAL có thể chọn lưu trữ tay điều khiển vùng đệm Gralloc tại đây.

IEvsCameraStream

Ứng dụng triển khai giao diện này để nhận các khung hình video không đồng bộ.

deliverFrame(BufferDesc buffer);

Nhận lệnh gọi từ HAL mỗi khi một khung hình video đã sẵn sàng để kiểm tra. Các giá trị xử lý vùng đệm mà phương thức này nhận được phải được trả về thông qua các lệnh gọi đến IEvsCamera::doneWithFrame(). Khi luồng video bị dừng bằng một lệnh gọi đến IEvsCamera::stopVideoStream(), lệnh gọi lại này có thể tiếp tục khi quy trình tiêu hao. Hệ thống vẫn phải trả về từng khung; khi khung cuối cùng trong luồng được phân phối, bufferHandle NULL sẽ được phân phối, cho biết điểm kết thúc luồng và không có thêm khung phân phối nào diễn ra. Bạn không cần gửi lại chính bufferHandle rỗng bằng doneWithFrame(), nhưng phải trả về tất cả các tay cầm khác

Mặc dù về mặt kỹ thuật, bạn có thể sử dụng các định dạng vùng đệm độc quyền, nhưng quy trình kiểm thử khả năng tương thích yêu cầu vùng đệm phải ở một trong 4 định dạng được hỗ trợ: NV21 (YCrCb 4:2:0 Bán phẳng), YV12 (YCrCb 4:2:0 Phẳng), YUYV (YCrCb 4:2:2 Lồng ghép), RGBA (32 bit R:G:B:x), BGRA (32 bit B:G:R:x). Định dạng đã chọn phải là nguồn hoạ tiết GL hợp lệ trên quá trình triển khai GLES của nền tảng.

Ứng dụng không được dựa vào bất kỳ mối tương ứng nào giữa trường bufferIdmemHandle trong cấu trúc BufferDesc. Về cơ bản, các giá trị bufferId là riêng tư đối với việc triển khai trình điều khiển HAL và trình điều khiển này có thể sử dụng (và sử dụng lại) các giá trị đó khi thấy phù hợp.

IEvsDisplay

Đối tượng này đại diện cho màn hình Evs, kiểm soát trạng thái của màn hình và xử lý cách trình bày hình ảnh thực tế.

getDisplayInfo() generates (DisplayDesc info);

Trả về thông tin cơ bản về màn hình EVS do hệ thống cung cấp (xem DisplayDesc).

setDisplayState(DisplayState state) generates (EvsResult result);

Đặt trạng thái hiển thị. Ứng dụng có thể đặt trạng thái hiển thị để thể hiện trạng thái mong muốn, đồng thời việc triển khai HAL phải chấp nhận yêu cầu một cách linh hoạt cho bất kỳ trạng thái nào trong khi ở bất kỳ trạng thái nào khác, mặc dù phản hồi có thể là bỏ qua yêu cầu.

Khi khởi chạy, màn hình được xác định để bắt đầu ở trạng thái NOT_VISIBLE, sau đó ứng dụng sẽ yêu cầu trạng thái VISIBLE_ON_NEXT_FRAME và bắt đầu cung cấp video. Khi màn hình không còn cần thiết, ứng dụng sẽ yêu cầu trạng thái NOT_VISIBLE sau khi truyền khung hình video cuối cùng.

Mã này hợp lệ cho mọi tiểu bang và có thể được yêu cầu bất cứ lúc nào. Nếu màn hình đã hiển thị, thì màn hình sẽ vẫn hiển thị nếu bạn đặt thành VISIBLE_ON_NEXT_FRAME. Luôn trả về OK trừ phi trạng thái được yêu cầu là một giá trị enum không được nhận dạng, trong trường hợp đó, INVALID_ARG sẽ được trả về.

getDisplayState() generates (DisplayState state);

Lấy trạng thái hiển thị. Việc triển khai HAL sẽ báo cáo trạng thái hiện tại thực tế, có thể khác với trạng thái được yêu cầu gần đây nhất. Logic chịu trách nhiệm thay đổi trạng thái hiển thị phải tồn tại phía trên lớp thiết bị, khiến việc triển khai HAL tự động thay đổi trạng thái hiển thị là không mong muốn.

getTargetBuffer() generates (handle bufferHandle);

Trả về một handle đến vùng đệm khung liên kết với màn hình. Phần mềm và/hoặc GL có thể khoá và ghi vào vùng đệm này. Bạn phải trả về vùng đệm này bằng lệnh gọi đến returnTargetBufferForDisplay() ngay cả khi màn hình không còn hiển thị.

Mặc dù về mặt kỹ thuật, bạn có thể sử dụng định dạng vùng đệm độc quyền, nhưng quy trình kiểm thử khả năng tương thích yêu cầu vùng đệm phải ở một trong 4 định dạng được hỗ trợ: NV21 (YCrCb 4:2:0 Bán phẳng), YV12 (YCrCb 4:2:0 Phẳng), YUYV (YCrCb 4:2:2 Lồng ghép), RGBA (32 bit R:G:B:x), BGRA (32 bit B:G:R:x). Định dạng đã chọn phải là mục tiêu kết xuất GL hợp lệ trong quá trình triển khai GLES của nền tảng.

Khi xảy ra lỗi, một bộ đệm có tay cầm rỗng sẽ được trả về, nhưng bạn không cần truyền bộ đệm đó trở lại returnTargetBufferForDisplay.

returnTargetBufferForDisplay(handle bufferHandle) generates (EvsResult result);

Cho màn hình biết vùng đệm đã sẵn sàng để hiển thị. Chỉ các vùng đệm được truy xuất thông qua lệnh gọi đến getTargetBuffer() mới hợp lệ để sử dụng với lệnh gọi này. Ứng dụng khách không thể sửa đổi nội dung của BufferDesc. Sau lệnh gọi này, ứng dụng không còn sử dụng được vùng đệm đó. Trả về OK khi thành công hoặc mã lỗi thích hợp có thể bao gồm INVALID_ARG hoặc BUFFER_NOT_AVAILABLE.

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

Mô tả các thuộc tính cơ bản của màn hình EVS và bắt buộc phải có khi triển khai EVS. HAL chịu trách nhiệm điền vào cấu trúc này để mô tả màn hình EVS. Có thể là màn hình thực hoặc màn hình ảo được phủ lên hoặc kết hợp với một thiết bị trình bày khác.

  • display_id. Một chuỗi nhận dạng duy nhất màn hình. Đây có thể là tên thiết bị hạt nhân của thiết bị hoặc tên của thiết bị, chẳng hạn như rearview (camera lùi). Giá trị cho chuỗi này được chọn bằng cách triển khai HAL và được ngăn xếp ở trên sử dụng một cách mờ.
  • vendor_flags. Phương thức truyền thông tin máy ảnh chuyên biệt một cách mờ từ trình điều khiển đến Ứng dụng EVS tuỳ chỉnh. Thông tin này được truyền không được diễn giải từ trình điều khiển đến ứng dụng EVS và ứng dụng này có thể bỏ qua thông tin đó.
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
}

Mô tả trạng thái của màn hình EVS. Màn hình này có thể tắt (người lái xe không nhìn thấy) hoặc bật (hiển thị hình ảnh cho người lái xe). Bao gồm trạng thái tạm thời, trong đó màn hình chưa hiển thị nhưng đã được chuẩn bị để hiển thị bằng cách phân phối khung hình ảnh tiếp theo bằng lệnh gọi returnTargetBufferForDisplay().

Trình quản lý EVS

Trình quản lý EVS cung cấp giao diện công khai cho hệ thống EVS để thu thập và trình bày chế độ xem máy ảnh bên ngoài. Trong trường hợp trình điều khiển phần cứng chỉ cho phép một giao diện đang hoạt động trên mỗi tài nguyên (máy ảnh hoặc màn hình), Trình quản lý EVS sẽ tạo điều kiện cho quyền truy cập chung vào máy ảnh. Một ứng dụng EVS chính là ứng dụng đầu tiên của Trình quản lý EVS và là ứng dụng duy nhất được phép ghi dữ liệu hiển thị (các ứng dụng bổ sung có thể được cấp quyền chỉ có thể đọc đối với hình ảnh của máy ảnh).

Trình quản lý EVS triển khai cùng một API như các trình điều khiển HAL cơ bản, đồng thời cung cấp dịch vụ mở rộng bằng cách hỗ trợ nhiều ứng dụng đồng thời (nhiều ứng dụng có thể mở máy ảnh thông qua Trình quản lý EVS và nhận luồng video).

Sơ đồ Trình quản lý EVS và
EVS Hardware API.
Hình 2. Trình quản lý EVS phản chiếu API Phần cứng EVS cơ bản.

Các ứng dụng không thấy sự khác biệt khi hoạt động thông qua việc triển khai HAL phần cứng EVS hoặc API Trình quản lý EVS, ngoại trừ việc API Trình quản lý EVS cho phép truy cập đồng thời vào luồng máy ảnh. Trình quản lý EVS chính là ứng dụng duy nhất được phép của lớp HAL phần cứng EVS và đóng vai trò là proxy cho HAL phần cứng EVS.

Các phần sau đây chỉ mô tả những lệnh gọi có hành vi khác (mở rộng) trong quá trình triển khai Trình quản lý EVS; các lệnh gọi còn lại giống với nội dung mô tả HAL EVS.

IEvsEnumerator

openCamera(string camera_id) generates (IEvsCamera camera);

Lấy đối tượng giao diện dùng để tương tác với một máy ảnh cụ thể được xác định bằng chuỗi camera_id duy nhất. Trả về giá trị NULL nếu không thành công. Ở lớp Trình quản lý EVS, miễn là có đủ tài nguyên hệ thống, một máy ảnh đã mở có thể được mở lại bằng một quy trình khác, cho phép phân luồng video đến nhiều ứng dụng tiêu dùng. Các chuỗi camera_id ở lớp Trình quản lý EVS giống với các chuỗi được báo cáo cho lớp Phần cứng EVS.

IEvsCamera

Trình quản lý EVS cung cấp việc triển khai IEvsCamera được ảo hoá nội bộ vì các thao tác trên máy ảnh của một ứng dụng không ảnh hưởng đến các ứng dụng khác, giúp giữ quyền truy cập độc lập vào máy ảnh của chúng.

startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);

Bắt đầu truyền phát video. Ứng dụng có thể độc lập bắt đầu và dừng luồng video trên cùng một máy ảnh cơ bản. Máy ảnh cơ bản sẽ khởi động khi ứng dụng đầu tiên khởi động.

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

Trả về một khung. Mỗi ứng dụng phải trả về khung hình khi hoàn tất, nhưng được phép giữ lại khung hình trong thời gian họ muốn. Khi số khung hình mà ứng dụng lưu giữ đạt đến giới hạn đã định cấu hình, ứng dụng sẽ không nhận được thêm khung hình nào cho đến khi trả về một khung hình. Việc bỏ qua khung hình này không ảnh hưởng đến các ứng dụng khác, các ứng dụng này sẽ tiếp tục nhận được tất cả khung hình như dự kiến.

stopVideoStream();

Dừng luồng video. Mỗi ứng dụng có thể dừng luồng video của mình bất cứ lúc nào mà không ảnh hưởng đến các ứng dụng khác. Luồng máy ảnh cơ bản ở lớp phần cứng sẽ ngừng khi ứng dụng cuối cùng của một máy ảnh nhất định ngừng luồng của máy ảnh đó.

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

Gửi một giá trị dành riêng cho trình điều khiển, có thể cho phép một ứng dụng ảnh hưởng đến một ứng dụng khác. Vì Trình quản lý EVS không thể hiểu được ý nghĩa của các từ điều khiển do nhà cung cấp xác định, nên các từ này không được ảo hoá và mọi hiệu ứng phụ đều áp dụng cho tất cả ứng dụng của một máy ảnh nhất định. Ví dụ: nếu một nhà cung cấp sử dụng lệnh gọi này để thay đổi tốc độ khung hình, thì tất cả ứng dụng của máy ảnh lớp phần cứng bị ảnh hưởng sẽ nhận được khung hình ở tốc độ mới.

IEvsDisplay

Chỉ được phép có một chủ sở hữu của màn hình, ngay cả ở cấp Người quản lý EVS. Trình quản lý không thêm chức năng nào và chỉ truyền trực tiếp giao diện IEvsDisplay đến hoạt động triển khai HAL cơ bản.

Ứng dụng EVS

Android bao gồm một bản triển khai tham chiếu C++ gốc của ứng dụng EVS giao tiếp với Trình quản lý EVS và HAL của xe để cung cấp các chức năng cơ bản của camera lùi. Ứng dụng dự kiến sẽ khởi động rất sớm trong quá trình khởi động hệ thống, với video phù hợp sẽ xuất hiện tuỳ thuộc vào các camera có sẵn và trạng thái xe (trạng thái của bánh răng và tín hiệu rẽ). OEM có thể sửa đổi hoặc thay thế ứng dụng EVS bằng bản trình bày và logic dành riêng cho xe.

Hình 3. Logic mẫu ứng dụng EVS, lấy danh sách camera.


Hình 4. Logic mẫu ứng dụng EVS, nhận lệnh gọi lại khung.

Vì dữ liệu hình ảnh được trình bày cho ứng dụng trong vùng đệm đồ hoạ tiêu chuẩn, nên ứng dụng sẽ chịu trách nhiệm di chuyển hình ảnh từ vùng đệm nguồn vào vùng đệm đầu ra. Mặc dù việc này làm tăng chi phí sao chép dữ liệu, nhưng cũng tạo cơ hội cho ứng dụng kết xuất hình ảnh vào vùng đệm hiển thị theo bất kỳ cách nào mà ứng dụng mong muốn.

Ví dụ: ứng dụng có thể chọn tự di chuyển dữ liệu pixel, có thể bằng phép toán tỷ lệ hoặc xoay nội tuyến. Ứng dụng cũng có thể chọn sử dụng hình ảnh nguồn làm hoạ tiết OpenGL và kết xuất một cảnh phức tạp vào vùng đệm đầu ra, bao gồm cả các phần tử ảo như biểu tượng, nguyên tắc và ảnh động. Một ứng dụng phức tạp hơn cũng có thể chọn nhiều máy ảnh đầu vào đồng thời và hợp nhất các máy ảnh đó vào một khung đầu ra duy nhất (chẳng hạn như để sử dụng trong chế độ xem ảo từ trên xuống về môi trường xung quanh xe).

Sử dụng EGL/SurfaceFlinger trong HAL hiển thị EVS

Phần này giải thích cách sử dụng EGL để hiển thị quá trình triển khai HAL Hiển thị EVS trong Android 10.

Quy trình triển khai tham chiếu HAL EVS sử dụng EGL để kết xuất bản xem trước của máy ảnh trên màn hình và sử dụng libgui để tạo nền tảng kết xuất EGL mục tiêu. Trên Android 8 (trở lên), libgui được phân loại là VNDK-private, là một nhóm các thư viện dành cho các thư viện VNDK mà quy trình của nhà cung cấp không thể sử dụng. Vì việc triển khai HAL phải nằm trong phân vùng của nhà cung cấp, nên nhà cung cấp không được sử dụng Surface trong quá trình triển khai HAL.

Xây dựng libgui cho các quy trình của nhà cung cấp

Việc sử dụng libgui là lựa chọn duy nhất để sử dụng EGL/SurfaceFlinger trong quá trình triển khai HAL hiển thị EVS. Cách đơn giản nhất để triển khai libgui là trực tiếp sử dụng frameworks/native/libs/gui bằng cách sử dụng một mục tiêu bản dựng bổ sung trong tập lệnh bản dựng. Mục tiêu này giống hệt với mục tiêu libgui, ngoại trừ việc thêm hai trường:

  • 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", ], …

Lưu ý: Mục tiêu của nhà cung cấp được tạo bằng macro NO_INPUT, macro này sẽ xoá một từ 32 bit khỏi dữ liệu gói. Vì SurfaceFlinger dự kiến trường này đã bị xoá, nên SurfaceFlinger không phân tích cú pháp được gói. Lỗi này được ghi nhận là lỗi 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

Cách giải quyết điều kiện này:

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);

Dưới đây là hướng dẫn tạo bản dựng mẫu. Dự kiến sẽ nhận được $(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

Sử dụng liên kết trong quá trình triển khai EVS HAL

Trong Android 8 (trở lên), nút thiết bị /dev/binder trở thành nút dành riêng cho các quy trình khung, do đó, các quy trình của nhà cung cấp không thể truy cập được. Thay vào đó, các quy trình của nhà cung cấp nên sử dụng /dev/hwbinder và phải chuyển đổi mọi giao diện AIDL sang HIDL. Đối với những người muốn tiếp tục sử dụng giao diện AIDL giữa các quy trình của nhà cung cấp, hãy sử dụng miền liên kết /dev/vndbinder.

Miền IPC Mô tả
/dev/binder IPC giữa các quy trình khung/ứng dụng có giao diện AIDL
/dev/hwbinder IPC giữa các quy trình khung/nhà cung cấp có giao diện HIDL
IPC giữa các quy trình của nhà cung cấp có giao diện HIDL
/dev/vndbinder IPC giữa các quy trình của nhà cung cấp/nhà cung cấp có Giao diện AIDL

Mặc dù SurfaceFlinger xác định các giao diện AIDL, nhưng các quy trình của nhà cung cấp chỉ có thể sử dụng các giao diện HIDL để giao tiếp với các quy trình khung. Bạn cần thực hiện một lượng công việc không nhỏ để chuyển đổi các giao diện AIDL hiện có thành HIDL. May mắn thay, Android cung cấp một phương thức để chọn trình điều khiển liên kết cho libbinder, liên kết với các quy trình thư viện không gian người dùng.

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));

Lưu ý: Các quy trình của nhà cung cấp phải gọi phương thức này trước khi gọi vào Process hoặc IPCThreadState hoặc trước khi thực hiện bất kỳ lệnh gọi liên kết nào.

Chính sách SELinux

Nếu quá trình triển khai thiết bị ở mức tối đa, SELinux sẽ ngăn các quy trình của nhà cung cấp sử dụng /dev/binder. Ví dụ: quá trình triển khai mẫu HAL EVS được chỉ định cho miền hal_evs_driver và yêu cầu quyền r/w đối với miền 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

Tuy nhiên, việc thêm các quyền này sẽ gây ra lỗi bản dựng vì vi phạm các quy tắc giá luôn không cho phép sau đây được xác định trong system/sepolicy/domain.te cho thiết bị âm thanh đầy đủ.

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 là một thuộc tính được cung cấp để phát hiện lỗi và hướng dẫn quá trình phát triển. Bạn cũng có thể sử dụng công cụ này để giải quyết lỗi vi phạm về Android 10 được mô tả ở trên.

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)

Xây dựng quy trình triển khai tham chiếu lớp trừu tượng phần cứng (HAL) cho EVS theo quy trình của nhà cung cấp

Để tham khảo, bạn có thể áp dụng các thay đổi sau cho packages/services/Car/evs/Android.mk. Hãy nhớ xác nhận rằng tất cả các thay đổi được mô tả đều phù hợp với việc triển khai của bạn.

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;