دوربین خودرو HAL

اندروید حاوی یک لایه انتزاعی سخت‌افزاری HIDL خودرو (HAL) است که امکان ضبط و نمایش تصاویر را در مراحل اولیه فرآیند بوت اندروید فراهم می‌کند و در طول عمر سیستم به کار خود ادامه می‌دهد. HAL شامل پشته سیستم نمای خارجی (EVS) است و معمولاً برای پشتیبانی از دوربین دید عقب و نمایشگرهای نمای اطراف در خودروهایی با سیستم‌های سرگرمی درون خودرویی (IVI) مبتنی بر اندروید استفاده می‌شود. EVS همچنین امکان پیاده‌سازی ویژگی‌های پیشرفته در برنامه‌های کاربر را فراهم می‌کند.

اندروید همچنین شامل یک رابط درایور ضبط و نمایش مخصوص EVS است (در /hardware/interfaces/automotive/evs/1.0 ). اگرچه ساخت یک برنامه دوربین دید عقب بر روی سرویس‌های دوربین و نمایش موجود اندروید امکان‌پذیر است، اما چنین برنامه‌ای احتمالاً در فرآیند بوت اندروید خیلی دیر اجرا می‌شود. استفاده از یک HAL اختصاصی، رابط کاربری ساده‌ای را فراهم می‌کند و مشخص می‌کند که یک OEM برای پشتیبانی از پشته EVS چه چیزی را باید پیاده‌سازی کند.

اجزای سیستم

EVS شامل اجزای سیستم زیر است:

نمودار اجزای سیستم EVS
شکل 1. نمای کلی اجزای سیستم EVS.

برنامه EVS

یک برنامه نمونه C++ EVS ( /packages/services/Car/evs/app ) به عنوان یک پیاده‌سازی مرجع عمل می‌کند. این برنامه مسئول درخواست فریم‌های ویدیویی از EVS Manager و ارسال فریم‌های تکمیل‌شده برای نمایش به EVS Manager است. انتظار می‌رود که به محض در دسترس بودن EVS و Car Service، ظرف دو (2) ثانیه پس از روشن شدن، توسط init آغاز شود. تولیدکنندگان اصلی تجهیزات (OEM) می‌توانند برنامه EVS را به دلخواه تغییر دهند یا جایگزین کنند.

مدیر EVS

EVS Manager ( /packages/services/Car/evs/manager ) بلوک‌های سازنده مورد نیاز یک برنامه EVS را برای پیاده‌سازی هر چیزی از یک نمایشگر دوربین عقب ساده گرفته تا رندر چند دوربینی 6DOF فراهم می‌کند. رابط کاربری آن از طریق HIDL ارائه می‌شود و برای پذیرش چندین کلاینت همزمان ساخته شده است. سایر برنامه‌ها و سرویس‌ها (به ویژه سرویس خودرو) می‌توانند وضعیت EVS Manager را جستجو کنند تا بفهمند سیستم EVS چه زمانی فعال است.

رابط EVS HIDL

سیستم EVS، شامل دوربین و عناصر نمایشگر، در بسته android.hardware.automotive.evs تعریف شده است. یک پیاده‌سازی نمونه که رابط را آزمایش می‌کند (تصاویر آزمایشی مصنوعی تولید می‌کند و تصاویر را در مسیر رفت و برگشت اعتبارسنجی می‌کند) در /hardware/interfaces/automotive/evs/1.0/default ارائه شده است.

تولیدکننده اصلی (OEM) مسئول پیاده‌سازی API بیان‌شده توسط فایل‌های .hal در /hardware/interfaces/automotive/evs است. چنین پیاده‌سازی‌هایی مسئول پیکربندی و جمع‌آوری داده‌ها از دوربین‌های فیزیکی و ارائه آن از طریق بافرهای حافظه مشترک قابل تشخیص توسط Gralloc هستند. سمت نمایشگر پیاده‌سازی مسئول ارائه یک بافر حافظه مشترک است که می‌تواند توسط برنامه پر شود (معمولاً با رندر EGL) و فریم‌های نهایی را در اولویت نسبت به هر چیز دیگری که ممکن است بخواهد روی صفحه نمایش فیزیکی ظاهر شود، ارائه می‌دهد. پیاده‌سازی‌های فروشنده رابط EVS ممکن است در /vendor/… /device/… یا hardware/… (مثلاً /hardware/[vendor]/[platform]/evs ) ذخیره شوند.

درایورهای هسته

دستگاهی که از پشته EVS پشتیبانی می‌کند، به درایورهای هسته نیاز دارد. به جای ایجاد درایورهای جدید، تولیدکنندگان اصلی تجهیزات (OEM) این گزینه را دارند که از ویژگی‌های مورد نیاز EVS از طریق درایورهای سخت‌افزار دوربین یا نمایشگر موجود پشتیبانی کنند. استفاده مجدد از درایورها می‌تواند مفید باشد، به خصوص برای درایورهای نمایشگر که ارائه تصویر ممکن است نیاز به هماهنگی با سایر رشته‌های فعال داشته باشد. اندروید ۸.۰ شامل یک درایور نمونه مبتنی بر v4l2 (در packages/services/Car/evs/sampleDriver ) است که برای پشتیبانی از v4l2 به هسته و برای ارائه تصویر خروجی به SurfaceFlinger وابسته است.

شرح رابط سخت‌افزاری 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 را گزارش می‌دهد (به API IEvsDisplay مراجعه کنید).

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 شروع به تحویل کنند و پس از شروع، باید حداقل با سرعت ۱۰ فریم در ثانیه تولید شوند. زمان لازم برای شروع پخش ویدئو عملاً جزو زمان مورد نیاز برای راه‌اندازی دوربین دید عقب محسوب می‌شود. اگر پخش آغاز نشود، باید یک کد خطا برگردانده شود؛ در غیر این صورت OK برگردانده می‌شود.

oneway doneWithFrame(BufferDesc buffer);

فریمی را که توسط IEvsCameraStream تحویل داده شده است، برمی‌گرداند. پس از اتمام مصرف فریمی که به رابط IEvsCameraStream تحویل داده شده است، فریم باید برای استفاده مجدد به IEvsCamera برگردانده شود. تعداد کم و محدودی بافر در دسترس است (احتمالاً به کوچکی یک عدد)، و اگر منبع تمام شود، فریم دیگری تا زمان برگرداندن یک بافر تحویل داده نمی‌شود، که به طور بالقوه منجر به فریم‌های از دست رفته می‌شود (بافری با شناسه تهی، پایان یک جریان را نشان می‌دهد و نیازی به برگرداندن آن از طریق این تابع نیست). در صورت موفقیت، مقدار 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 باید با این ساختار به صورت فقط خواندنی رفتار کند. این فیلدها حاوی اطلاعات کافی هستند تا به کلاینت اجازه دهند یک شیء ANativeWindowBuffer را بازسازی کند، همانطور که ممکن است برای استفاده از تصویر با EGL با پسوند eglCreateImageKHR() مورد نیاز باشد.

  • 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 را در اینجا ذخیره کند.

دوربین IevsStream

کلاینت این رابط را برای دریافت فریم‌های ویدیویی ناهمزمان پیاده‌سازی می‌کند.

deliverFrame(BufferDesc buffer);

هر بار که یک فریم ویدیویی برای بررسی آماده می‌شود، فراخوانی‌هایی از HAL دریافت می‌کند. دسته‌های بافر دریافت شده توسط این متد باید از طریق فراخوانی‌های IEvsCamera::doneWithFrame() برگردانده شوند. هنگامی که جریان ویدیو با فراخوانی IEvsCamera::stopVideoStream() متوقف می‌شود، این فراخوانی ممکن است تا زمانی که خط لوله تخلیه شود، ادامه یابد. هر فریم همچنان باید برگردانده شود. هنگامی که آخرین فریم در جریان تحویل داده شد، یک bufferHandle NULL تحویل داده می‌شود که نشان‌دهنده پایان جریان است و دیگر تحویل فریمی رخ نمی‌دهد. خود bufferHandle NULL نیازی به ارسال با doneWithFrame() ندارد، اما همه دسته‌های دیگر باید برگردانده شوند.

اگرچه از نظر فنی امکان استفاده از فرمت‌های بافر اختصاصی وجود دارد، اما آزمایش سازگاری مستلزم آن است که بافر در یکی از چهار فرمت پشتیبانی شده باشد: NV21 (YCrCb 4:2:0 Semi-Planar)، YV12 (YCrCb 4:2:0 Planar)، YUYV (YCrCb 4:2:2 Interleaved)، RGBA (32 bit R:G:B:x)، BGRA (32 bit B:G:R:x). فرمت انتخاب شده باید یک منبع بافت GL معتبر در پیاده‌سازی GLES پلتفرم باشد.

این برنامه نباید به هیچ ارتباطی بین فیلد bufferId و memHandle در ساختار BufferDesc متکی باشد. مقادیر 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 Semi-Planar)، YV12 (YCrCb 4:2:0 Planar)، YUYV (YCrCb 4:2:2 Interleaved)، RGBA (32 bit R:G:B:x)، BGRA (32 bit B:G:R:x). فرمت انتخاب شده باید یک هدف رندر GL معتبر در پیاده‌سازی GLES پلتفرم باشد.

در صورت بروز خطا، یک بافر با شناسه‌ی 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 همان API درایورهای HAL اصلی را پیاده‌سازی می‌کند و با پشتیبانی از چندین کلاینت همزمان، خدمات گسترده‌تری ارائه می‌دهد (بیش از یک کلاینت می‌تواند از طریق EVS Manager یک دوربین را باز کند و یک جریان ویدیویی دریافت کند).

نمودار رابط برنامه‌نویسی نرم‌افزار EVS Manager و EVS Hardware API.
شکل ۲. EVS Manager، رابط برنامه‌نویسی کاربردی سخت‌افزاری EVS را منعکس می‌کند.

برنامه‌ها هنگام کار از طریق پیاده‌سازی EVS Hardware HAL یا EVS Manager API هیچ تفاوتی نمی‌بینند، به جز اینکه EVS Manager API امکان دسترسی همزمان به جریان دوربین را فراهم می‌کند. EVS Manager خود، تنها کلاینت مجاز لایه EVS Hardware HAL است و به عنوان یک پروکسی برای EVS Hardware HAL عمل می‌کند.

بخش‌های زیر فقط فراخوانی‌هایی را شرح می‌دهند که رفتار متفاوتی (توسعه‌یافته) در پیاده‌سازی EVS Manager دارند؛ سایر فراخوانی‌ها با توضیحات EVS HAL یکسان هستند.

IevsEnumerator

openCamera(string camera_id) generates (IEvsCamera camera);

یک شیء رابط کاربری را که برای تعامل با یک دوربین خاص که توسط رشته منحصر به فرد camera_id شناسایی شده است، دریافت می‌کند. در صورت عدم موفقیت، مقدار NULL را برمی‌گرداند. در لایه EVS Manager، تا زمانی که منابع سیستمی کافی در دسترس باشد، دوربینی که از قبل باز است، می‌تواند دوباره توسط فرآیند دیگری باز شود و امکان اتصال جریان ویدیو به چندین برنامه کاربردی را فراهم کند. رشته‌های camera_id در لایه EVS Manager همان رشته‌هایی هستند که به لایه EVS Hardware گزارش می‌شوند.

دوربین 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 نیز فقط یک مالک نمایشگر مجاز است. این مدیر هیچ عملکردی اضافه نمی‌کند و صرفاً رابط IevsDisplay را مستقیماً به پیاده‌سازی HAL زیربنایی منتقل می‌کند.

برنامه EVS

اندروید شامل یک پیاده‌سازی مرجع بومی C++ از یک برنامه EVS است که با EVS Manager و Vehicle HAL ارتباط برقرار می‌کند تا عملکردهای اولیه دوربین دید عقب را ارائه دهد. انتظار می‌رود این برنامه خیلی زود در فرآیند بوت سیستم شروع به کار کند و بسته به دوربین‌های موجود و وضعیت خودرو (وضعیت دنده و چراغ راهنما)، ویدیوی مناسبی نمایش داده شود. تولیدکنندگان اصلی تجهیزات (OEM) می‌توانند برنامه EVS را با منطق و نحوه نمایش خاص خودرو خود تغییر دهند یا جایگزین کنند.

شکل ۳. منطق نمونه برنامه EVS، دریافت لیست دوربین‌ها.


شکل ۴. منطق نمونه برنامه EVS، فراخوانی فریم دریافتی.

از آنجا که داده‌های تصویر در یک بافر گرافیکی استاندارد به برنامه ارائه می‌شوند، برنامه مسئول انتقال تصویر از بافر منبع به بافر خروجی است. اگرچه این کار هزینه کپی داده را افزایش می‌دهد، اما این فرصت را نیز برای برنامه فراهم می‌کند تا تصویر را به هر روشی که می‌خواهد در بافر نمایش رندر کند.

برای مثال، برنامه ممکن است تصمیم بگیرد که خود داده‌های پیکسلی را جابجا کند، احتمالاً با یک عملیات مقیاس‌بندی یا چرخش درون‌خطی. برنامه همچنین می‌تواند از تصویر منبع به عنوان یک بافت OpenGL استفاده کند و یک صحنه پیچیده، شامل عناصر مجازی مانند آیکون‌ها، خطوط راهنما و انیمیشن‌ها را به بافر خروجی رندر کند. یک برنامه پیچیده‌تر همچنین ممکن است چندین دوربین ورودی همزمان را انتخاب کرده و آنها را در یک فریم خروجی واحد ادغام کند (مانند استفاده در یک نمای مجازی از بالا به پایین از محیط اطراف خودرو).

از EGL/SurfaceFlinger در EVS Display HAL استفاده کنید

این بخش نحوه استفاده از EGL برای رندر کردن پیاده‌سازی EVS Display HAL در اندروید ۱۰ را توضیح می‌دهد.

یک پیاده‌سازی مرجع EVS HAL از EGL برای رندر کردن پیش‌نمایش دوربین روی صفحه و از libgui برای ایجاد سطح رندر EGL هدف استفاده می‌کند. در اندروید ۸ (و بالاتر)، libgui به عنوان VNDK-private طبقه‌بندی می‌شود که به گروهی از کتابخانه‌های موجود برای کتابخانه‌های VNDK اشاره دارد که فرآیندهای فروشنده نمی‌توانند از آنها استفاده کنند. از آنجا که پیاده‌سازی‌های HAL باید در پارتیشن فروشنده قرار گیرند، فروشندگان نمی‌توانند از Surface در پیاده‌سازی‌های HAL استفاده کنند.

ساخت libgui برای فرآیندهای فروشنده

استفاده از libgui تنها گزینه برای استفاده از EGL/SurfaceFlinger در پیاده‌سازی‌های EVS Display HAL است. ساده‌ترین راه برای پیاده‌سازی libgui از طریق frameworks/native/libs/gui به طور مستقیم با استفاده از یک هدف ساخت اضافی در اسکریپت ساخت است. این هدف دقیقاً مشابه هدف libgui است، به جز اضافه شدن دو فیلد:

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

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

توجه: اهداف فروشنده با ماکروی NO_INPUT ساخته می‌شوند که یک کلمه ۳۲ بیتی را از داده‌های بسته حذف می‌کند. از آنجا که 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

استفاده از binder در پیاده‌سازی EVS HAL

در اندروید ۸ (و بالاتر)، گره دستگاه /dev/binder منحصر به فرآیندهای چارچوب شد و بنابراین، برای فرآیندهای فروشنده غیرقابل دسترسی شد. در عوض، فرآیندهای فروشنده باید از /dev/hwbinder استفاده کنند و باید هرگونه رابط AIDL را به HIDL تبدیل کنند. کسانی که می‌خواهند به استفاده از رابط‌های AIDL بین فرآیندهای فروشنده ادامه دهند، از دامنه binder، /dev/vndbinder ، استفاده کنند.

دامنه IPC توضیحات
/dev/binder IPC بین فرآیندهای چارچوب/برنامه با رابط‌های AIDL
/dev/hwbinder IPC بین فرآیندهای چارچوب/فروشنده با رابط‌های HIDL
IPC بین فرآیندهای فروشنده با رابط‌های HIDL
/dev/vndbinder IPC بین فرآیندهای فروشنده/فروشنده با رابط‌های AIDL

در حالی که SurfaceFlinger رابط‌های AIDL را تعریف می‌کند، فرآیندهای فروشنده فقط می‌توانند از رابط‌های HIDL برای ارتباط با فرآیندهای چارچوب استفاده کنند. برای تبدیل رابط‌های AIDL موجود به HIDL، مقدار قابل توجهی کار لازم است. خوشبختانه، اندروید روشی را ارائه می‌دهد که با آن می‌توان درایور اتصال‌دهنده را برای 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 یا قبل از انجام هرگونه فراخوانی binder فراخوانی کنند.

سیاست‌های SELinux

اگر پیاده‌سازی دستگاه کاملاً سه‌گانه باشد، SELinux از استفاده فرآیندهای فروشنده از /dev/binder جلوگیری می‌کند. برای مثال، یک پیاده‌سازی نمونه EVS HAL به دامنه hal_evs_driver اختصاص داده شده و به مجوزهای r/w برای دامنه 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

با این حال، افزودن این مجوزها باعث شکست در ساخت می‌شود زیرا قوانین neverallow زیر را که در system/sepolicy/domain.te برای یک دستگاه full-treble تعریف شده‌اند، نقض می‌کند.

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 یک ویژگی است که برای شناسایی یک اشکال و هدایت توسعه ارائه شده است. همچنین می‌تواند برای رفع نقص اندروید ۱۰ که در بالا توضیح داده شد، مورد استفاده قرار گیرد.

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;