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 hiển thị và chụp dành riêng cho EVS (trong /hardware/interfaces/automotive/evs/1.0). Mặc dù có thể tạo ứng dụng camera lùi dựa trên các dịch vụ hiển thị và máy ảnh Android hiện có, nhưng ứng dụng như vậy có thể chạy quá muộn trong quá trình khởi động Android. Việc sử dụng HAL chuyên dụng giúp tạo nên một giao diện tinh giản và nêu rõ những gì OEM cần triển khai để hỗ trợ ngăn xếp EVS.

Thành phần hệ thống

EVS có các thành phần hệ thống sau đây:

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. Ứng dụng này dự kiến sẽ được khởi động bằng khởi tạo ngay khi có EVS và Dịch vụ ô tô, được nhắm mục tiêu 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 lớp 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 HIDL EVS

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 phương thức triển khai mẫu nhằm thực hiện giao diện (tạo hình ảnh kiểm thử tổng hợp và xác thực các hình ảnh thực hiện trọn vòng) được cung cấp trong /hardware/interfaces/automotive/evs/1.0/default.

OEM chịu trách nhiệm triển khai API được biểu thị bằng các tệp .hal 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 đó 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 chịu 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à thông qua tính năng 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 nội dung 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 kernel

Một 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 hiển thị và/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ả 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ả sử nhóm máy ảnh được cố định và có thể 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 đố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 khi 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ể camera đã bị giành trước theo cách này phải được đặt ở trạng thái không hoạt động, đang chờ huỷ bỏ lần cuối và phản hồi mọi yêu cầu ảnh hưởng đến trạng thái của camera 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 camera 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 khách có thể 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à sẽ 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 bất kỳ thao tác đột biến nào khi đã chết. Cuối cùng, ứng dụng khách sẽ nhận thấy mã trả về lỗi OWNERSHIP_LOST và đóng cũng như 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 qua 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);

Xem 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 ứng dụng 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. Phương thức truyền thông tin máy ảnh chuyên biệt một cách mờ đục từ trình điều khiển sang một ứng dụng EVS tuỳ chỉnh. Phương thức này được truyền mà không diễn giải từ trình điều khiển đến ứng dụng EVS để bạn có thể thoải mái bỏ qua.

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ợ. Tối đa là nhiều khung hình có thể được ứng dụng khách của IEvsCamera giữ đồng thời. 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 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 không bắt đầu, hệ thống phải trả về một mã lỗi; nếu không, hệ thống 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 của 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 thời gian sau khi lệnh gọi này trở lại. 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 cho mọi opaqueIdentifier không được nhận dạng.

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 trình điều khiển nhận dạng và chấp nhận các giá trị, thì hệ thống sẽ trả về OK; nếu không, hệ thống 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 thông qua 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 được chiếm bởi mỗi pixel riêng lẻ, cho phép tính toán kích thước bằng số 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 đặt. 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 phân phối khung 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. Bạn phải trả về các tay điều khiển bộ đệm mà phương thức này nhận được thông qua các lệnh gọi đến IEvsCamera::doneWithFrame(). Khi luồng video bị dừng thông qua 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 kết thúc. Bạn vẫn phải trả về từng khung hình; khi khung hình cuối cùng trong luồng đã được phân phối, một bufferHandle RỖNG sẽ được phân phối, cho biết luồng đã kết thúc và không có khung hình nào được phân phối nữa. Bản thân bộ đệm NULL không cần gửi lại qua doneWithFrame(), nhưng phải trả về tất cả các tên người dùng 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 nên dựa vào bất kỳ sự 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 được đặt ở chế độ riêng tư đối với quá trình triển khai trình điều khiển HAL và có thể sử dụng (cũng như 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ý việc 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 và việc triển khai HAL phải chấp nhận một cách linh hoạt yêu cầu về 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 dự kiến sẽ yêu cầu trạng thái VISIBLE_ON_NEXT_FRAME và bắt đầu cung cấp video. Khi không còn cần màn hình hiển thị, ứng dụng dự kiến sẽ yêu cầu trạng thái NOT_VISIBLE sau khi truyền khung video gần đây nhất.

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

Xem 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ị, điều này không mong muốn khi triển khai HAL (Lớp trừu tượng phần cứng) để thay đổi tự phát trạng thái hiển thị.

getTargetBuffer() generates (handle bufferHandle);

Trả về một handle đến vùng đệm khung liên kết với màn hình. Bộ đệm này có thể bị phần mềm và/hoặc GL khoá và ghi vào. Vùng đệm này phải được trả về thông qua 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ệ trên cách triển khai GLES của nền tảng.

Khi xảy ra lỗi, một vùng đệm có tay cầm rỗng sẽ được trả về, nhưng bạn không cần truyền vùng đệ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ỉ những bộ đệ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 và ứng dụng khách không được sửa đổi nội dung của BufferDesc. Sau lệnh gọi này, ứng dụng khách sẽ không thể sử dụng bộ đệm nữa. 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 có 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. 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 đó.
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, có thể bị tắt (không hiển thị cho người lái xe) hoặc bật (hiển thị hình ảnh cho người lái xe). Bao gồm một 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 thông qua 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à hiển thị khung hiển thị 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 với trình điều khiển HAL cơ bản và 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à API phần cứng EVS.

Hình 2. Trình quản lý EVS phản ánh 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.

Hàm 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 EVS Manager cũng giống như các chuỗi được báo cáo cho lớp EVS Hardware.

IEvsCamera

Trình quản lý EVS đã cung cấp việc triển khai IEvsCamera được ảo hoá nội bộ để 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, nhờ đó giữ lại 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 khách có thể dừng luồng video bất cứ lúc nào mà không ảnh hưởng đến các ứng dụng khách 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 người lái xe, 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 khách 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 cách triển khai tham chiếu C++ gốc của một ứng dụng EVS giao tiếp với Trình quản lý EVS và HAL xe để cung cấp các chức năng cơ bản của camera chiếu sau. Ứng dụng dự kiến sẽ bắt đầu rất sớm trong quá trình khởi động hệ thống, với video phù hợp hiển thị tuỳ thuộc vào các máy ảnh có sẵn và trạng thái của ô tô (trạng thái của cần số và xi nhan). 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 máy ảnh.



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 để kết xuất cách triển khai lớp trừu tượng phần cứng (HAL) cho màn hình 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. Trong Android 8 (trở lên), libgui được phân loại là VNDK-private, nghĩa là một nhóm thư viện có sẵn cho các thư viện VNDK mà các 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à thông qua frameworks/native/libs/gui trực tiếp 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 tình trạng 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 một $(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 phải 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 việc triển khai thiết bị là âm cao đầy đủ, thì 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ẽ khiến bản dựng không thành công vì vi phạm các quy tắc neverallow sau đây được xác định trong system/sepolicy/domain.te cho thiết bị có dải âm cao đầ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ể dùng công cụ này để giải quyết lỗi vi phạm trên Android 10 như 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 HAL EVS dưới dạng 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 hoạt động khi bạn triển khai.

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;