طبقة تجريد الأجهزة (HAL) لكاميرا المركبات

يحتوي Android على طبقة HIDL Hardware Abstraction Layer (HAL) للسيارات التي توفّر إمكانية التقاط الصور وعرضها في مرحلة مبكرة جدًا من عملية تشغيل Android وتستمر في العمل طوال مدة استخدام النظام. يتضمّن HAL حِزمة نظام العرض الخارجي (EVS) ويُستخدَم عادةً لإتاحة كاميرا الرؤية الخلفي ومشاهد العرض المحيطي في المركبات المزوّدة بأنظمة تكامل الترفيه والمعلومات (IVI) في المركبات والمستندة إلى نظام التشغيل Android. تمكّن EVS أيضًا من تنفيذ الميزات المتقدّمة في تطبيقات المستخدم.

يتضمّن Android أيضًا واجهة برنامج تشغيل العرض والتقاط الصور الخاصة بـ EVS (في تطبيق /hardware/interfaces/automotive/evs/1.0). وعلى الرغم من أنّه من الممكن إنشاء تطبيق كاميرا للرؤية الخلفية فوق خدمات العرض والكاميرا الحالية التي تعمل بنظام التشغيل Android، من المحتمل أن يتأخر تشغيل هذا التطبيق في عملية تشغيل Android. يتيح استخدام HAL مخصّص واجهة سلسة ويوضّح ما يحتاج إليه المصنّع الأصلي للجهاز لتوفير حِزمة EVS.

مكونات النظام

يتضمّن نظام EVS مكونات النظام التالية:

مخطط مكونات
نظام EVS

الشكل 1. نظرة عامة على مكوّنات نظام EVS

تطبيق EVS

يُعدّ تطبيق EVS لنظام التشغيل C++ (/packages/services/Car/evs/app) نموذجاً لتنفيذ المراجع. يتولّى هذا التطبيق طلب لقطات الفيديو من مدير EVS وإرسال اللقطات المكتملة للعرض مرة أخرى إلى مدير EVS. ومن المتوقّع أن يبدأ تشغيل الخدمة فور توفّر خدمة EVS وخدمة السيارة، ويتم استهدافه في غضون ثانيتَين (2) من التشغيل. يمكن لمصنّعي المعدّات الأصلية تعديل تطبيق EVS أو استبداله حسب الرغبة.

أداة إدارة EVS

يقدّم "مدير EVS" (/packages/services/Car/evs/manager) العناصر الأساسية التي يحتاجها تطبيق EVS لتنفيذ أيّ شيء، بدءًا من عرض كاميرا الرؤية الخلفية البسيطة ووصولاً إلى عرض متعدّد الكاميرات بتقنية 6 درجات من الحرية. يتم عرض واجهته من خلال HIDL وهي مصمّمة لقبول عملاء متعدّدين متزامنين. يمكن للتطبيقات والخدمات الأخرى (على وجه التحديد خدمة Car Service) طلب حالة EVS Manager لمعرفة وقت نشاط نظام EVS.

واجهة EVS HIDL

يتم تحديد نظام EVS، بما في ذلك الكاميرا وعناصر العرض، في حزمة android.hardware.automotive.evs. في /hardware/interfaces/automotive/evs/1.0/default، يتوفّر نموذج تنفيذ يختبر الواجهة (يُنشئ صور اختبارية اصطناعية ويتحقّق مما إذا كانت الصور تُجري عملية النقل ذهابًا وإيابًا).

المصنّع الأصلي للجهاز مسؤول عن تنفيذ واجهة برمجة التطبيقات التي يتم التعبير عنها من خلال ملفات .hal في /hardware/interfaces/automotive/evs. وتكون عمليات التنفيذ هذه مسؤولة عن إعداد البيانات وجمعها من الكاميرات الفعلية وتقديمها عبر مخازن ذاكرة مشتركة يمكن التعرف عليها من خلال Gralloc. إنّ جانب الشاشة من التنفيذ مسؤول عن توفير ذاكرة تخزين مؤقتة مشترَكة يمكن للتطبيق ملؤها (عادةً من خلال عرض EGL) وعرض اللقطات المكتملة بدلاً من أي شيء آخر قد تريد الظهور على الشاشة الفعلية. قد يتم تخزين عمليات تنفيذ المورِّد لواجهة EVS ضمن /vendor/… /device/… أو hardware/… (على سبيل المثال، /hardware/[vendor]/[platform]/evs).

برامج تشغيل النواة

يتطلب الجهاز المتوافق مع حِزمة EVS برامج تشغيل نواة. بدلاً من إنشاء برامج تشغيل جديدة، يمكن للمصنعين الأصليين للأجهزة تفعيل الميزات المطلوبة لمعايير EVS من خلال برامج تشغيل الأجهزة الحالية للكاميرا و/أو الشاشة. يمكن أن تكون إعادة استخدام برامج التشغيل مفيدة، خاصةً لبرامج تشغيل العرض التي قد تتطلب عرض الصور التنسيق مع سلاسل التعليمات النشطة الأخرى. يتضمّن الإصدار 8.0 من نظام التشغيل Android ملفًا نموذجيًا لبرنامج تشغيل يستند إلى v4l2 (في packages/services/Car/evs/sampleDriver) ويعتمد على kernel لإتاحة v4l2 وعلى SurfaceFlinger لعرض الصورة الناتجة.

وصف واجهة الأجهزة في نظام EVS

يصف القسم HAL. من المتوقّع أن يوفّر المورّدون عمليات تنفيذ لواجهة برمجة التطبيقات هذه مُعدّلة لأجهزةهم.

معادلة 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()). يجب أولاً إيقاف بث الفيديو من الكاميرا من خلال الاتصال برقم stopVideoStream() قبل الاتصال برقم closeCamera.

openDisplay() generates (IEvsDisplay display);

الحصول على عنصر واجهة يُستخدَم للتفاعل حصريًا مع شاشة EVS في النظام يمكن لعميل واحد فقط الاحتفاظ بمثيل وظيفي من IEvsDisplay في كل وقت. على غرار سلوك الفتح العدواني الموضّح في openCamera، قد يتم إنشاء عنصر IEvsDisplay جديد في أي وقت، وسيؤدي ذلك إلى إيقاف أي مثيلات سابقة. تظل النُسخ التي تم إلغاء صلاحيتها موجودة وتستجيب لطلبات تشغيل الدوالّ من مالكيها، ولكن يجب ألا تُجري أي عمليات تغيُّر عندما تكون غير نشطة. وفي النهاية، من المتوقّع أن يلاحظ تطبيق العميل ظهور أخطاء OWNERSHIP_LOST ورموز الإرجاع ويغلق الواجهة غير النشطة ويطلقها.

closeDisplay(IEvsDisplay display);

تُستخدَم لتحرير واجهة IEvsDisplay (وهي عكس طلب openDisplay()). يجب إعادة الموارد الاحتياطية المعلّقة التي يتم تلقّيها من خلال طلبات "getTargetBuffer()" إلى الشاشة قبل إغلاق الشاشة.

getDisplayState() generates (DisplayState state);

الحصول على حالة العرض الحالية من المفترض أن يُبلغ تنفيذ HAL عن الحالة الراهنة الحالية، والتي قد تختلف عن الحالة المطلوبة مؤخرًا. يجب أن يكون المنطق المسؤول عن تغيير حالات العرض أعلى طبقة الجهاز، ما يجعل من غير المرغوب فيه أن يؤدي تنفيذ HAL إلى تغيير حالات العرض بشكل عفوي. إذا لم يكن أي عميل يملك حاليًا العرض (من خلال طلب برمجي لمحاولة openDisplay)، ستعرض هذه الدالة القيمة NOT_OPEN. بخلاف ذلك، يتم إعلامك بالحالة الحالية لشاشة EVS (اطّلِع على IEvsDisplay API).

struct CameraDesc {
    string      camera_id;
    int32       vendor_flags;       // Opaque value
}
  • camera_id. سلسلة تحدِّد كاميرا معيّنة بشكل فريد. يمكن أن يكون اسم جهاز kernel للجهاز أو اسمًا للجهاز، مثل rearview. يتم اختيار قيمة هذه السلسلة من خلال تنفيذ HAL ويتم استخدامها بشكل غير شفاف بواسطة الحزمة أعلاه.
  • vendor_flags. طريقة لنقل معلومات كاميرا متخصصة بشكل غير واضح من برنامج التشغيل إلى تطبيق EVS مخصّص. ويتم نقلها بدون تفسير من برنامج التشغيل إلى تطبيق EVS الذي يمكنه تجاهلها.

IEvsCamera

يمثّل هذا العنصر كاميرا واحدة، وهو الواجهة الأساسية لالتقاط الصور.

getCameraInfo() generates (CameraDesc info);

عرض CameraDesc من هذه الكاميرا

setMaxFramesInFlight(int32 bufferCount) generates (EvsResult result);

يحدِّد عمق سلسلة المخزن المؤقت المطلوب من الكاميرا أن تتوافق معه. يمكن أن يحتفظ عميل IEvsCamera بحدٍ أقصى من هذا العدد من اللقطات في الوقت نفسه. إذا تم تسليم هذا العدد الكبير من اللقطات إلى المُستلِم بدون إرجاعها من قِبل doneWithFrame، يتخطّى البث اللقطات إلى أن يتم إرجاع ذاكرة تخزين مؤقت لإعادة استخدامها. من القانوني أن يصل هذا الطلب في أي وقت، حتى أثناء تنفيذ عمليات البث، وفي هذه الحالة يجب إضافة أو إزالة وحدات التخزين المؤقت من السلسلة حسب الاقتضاء. في حال عدم إجراء أي مكالمة إلى نقطة الدخول هذه، تتيح IEvsCamera لقطة واحدة على الأقل تلقائيًا، مع قبول المزيد.

إذا تعذّر استيفاء عدد الفواصل المطلوبة، تُرجع الدالة BUFFER_NOT_AVAILABLE أو رمز خطأ آخر ذي صلة. في هذه الحالة، يواصل النظام العمل بالقيمة التي تم ضبطها سابقًا.

startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);

يطلب إرسال لقطات كاميرا EVS من هذه الكاميرا. يبدأ IEvsCameraStream بتلقّي مكالمات دورية تتضمّن إطارات صور جديدة إلى أن يتم استدعاء stopVideoStream(). يجب أن يبدأ عرض اللقطات في غضون 500 ملي ثانية من طلب startVideoStream، وبعد البدء، يجب أن يتم إنشاؤها بمعدّل 10 لقطات في الثانية على الأقل. يتم احتساب الوقت اللازم لبدء بث الفيديو بشكل فعّال مقابل أي متطلبات زمنية لبدء تشغيل كاميرا الرؤية الخلفية. إذا لم يبدأ البث، يجب عرض رمز خطأ، وإلا سيتم عرض "حسنًا".

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 خاصة ببرنامج التشغيل، ولكن قد لا تؤدي أي قيمة تم تمريرها إلى تعطُّل برنامج التشغيل. يجب أن يعرض برنامج تشغيل الجهاز القيمة 0 لأي opaqueIdentifier غير معروف.

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
}

يصف صورة تم تمريرها من خلال واجهة برمجة التطبيقات. يكون محرك 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 للسماح بالتعرّف على مخزن مؤقت بعد رحلة ذهاب وإياب من خلال واجهات برمجة تطبيقات HAL ويمكن اختيار القيمة المخزنة في هذا الحقل عشوائيًا من خلال تنفيذ HAL.
  • memHandle. معرّف وحدة تخزين مؤقت للذاكرة الأساسية التي تحتوي على بيانات الصورة. قد يختار مطوّر HAL تخزين معرّف ملف تخزين مؤقت Gralloc هنا.

IEvsCameraStream

ينفِّذ العميل هذه الواجهة لتلقّي عمليات إرسال إطارات الفيديو غير المتزامنة.

deliverFrame(BufferDesc buffer);

تتلقّى طلبات من HAL في كل مرة يصبح فيها إطار فيديو جاهزًا للفحص. يجب إرجاع معالِم التخزين المؤقت الذي تتلقّاه هذه الطريقة من خلال طلبات إلى IEvsCamera::doneWithFrame(). عند إيقاف بث الفيديو من خلال مكالمة إلى IEvsCamera::stopVideoStream()، قد يستمر هذا المرجع المتغيّر مع تفريغ مسار الإرسال. يجب أن يستمر عرض كل لقطة. وعند تسليم اللقطة الأخيرة في البث، سيتم تسليم قيمة NULL لـ bufferHandle، مما يشير إلى نهاية البث ولن يتم تسليم المزيد من اللقطات. لا يلزم إعادة إرسال قيمة NULL bufferHandle نفسها من خلال doneWithFrame()، ولكن يجب إرجاع جميع العناصر الأخرى.

على الرغم من أنّ تنسيقات المخزن المؤقت التي تملكها جهة خارجية ممكنة من الناحية الفنية، يتطلّب اختبار التوافق أن يكون المخزن المؤقت بأحد التنسيقات الأربعة المتوافقة: NV21 (YCrCb 4:2:0 شبه مسطّح)، وYV12 (YCrCb 4:2:0 مسطّح)، وYUYV (YCrCb 4:2:2 مُدرَج)، وRGBA (32 بت R:G:B:x)، وBGRA (32 بت B:G:R:x). يجب أن يكون التنسيق المحدد مصدر زخرفة GL صالح على تنفيذ GLES للنظام الأساسي.

يجب عدم اعتماد التطبيق على أي مطابقة بين الحقل bufferId وmemHandle في بنية BufferDesc. إنّ قيم bufferId هي خاصة بشكل أساسي بتنفيذ برنامج تشغيل HAL، وقد يستخدمه (ويعيد استخدامه) حسبما يراه مناسبًا.

IEvsDisplay

يمثل هذا الكائن شاشة Evs ويتحكم في حالة الشاشة ويعالج العرض الفعلي للصور.

getDisplayInfo() generates (DisplayDesc info);

تعرِض هذه السمة معلومات أساسية عن شاشة EVS التي يوفّرها النظام (راجِع DisplayDesc).

setDisplayState(DisplayState state) generates (EvsResult result);

لضبط حالة العرض ويمكن للعملاء ضبط حالة العرض للتعبير عن الحالة المطلوبة، ويجب أن يقبل تنفيذ HAL الطلب بسلاسة لأي حالة أثناء أي حالة أخرى، على الرغم من أن الاستجابة قد تكون تجاهل الطلب.

عند الإعداد، يتم ضبط الشاشة على أن تبدأ في حالة NOT_VISIBLE، ومن المتوقّع أن يطلب العميل بعد ذلك حالة VISIBLE_ON_NEXT_FRAME ويبدأ في تقديم الفيديو. عندما لا يعود مطلوبًا عرض المحتوى، من المتوقّع أن يطلب العميل حالة NOT_VISIBLE بعد مرور آخر إطار من الفيديو.

ويُسمح بطلب أي حالة في أي وقت. إذا كانت الشاشة معروضة حاليًا، من المفترض أن تظل معروضة إذا تم ضبطها على VISIBLE_ON_NEXT_FRAME. يتم دائمًا عرض القيمة "حسنًا" ما لم تكن الحالة المطلوبة هي قيمة قائمة أرقام صحيحة غير معروفة، وفي هذه الحالة يتم عرض القيمة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 على المنصة.

في حال حدوث خطأ، يتم إرجاع مخزن مؤقت يتضمّن معرّفًا غير صالح، ولكن لا ينبغي إرسال هذا المخزن المؤقت مرة أخرى إلى returnTargetBufferForDisplay.

returnTargetBufferForDisplay(handle bufferHandle) generates (EvsResult result);

تُعلم الشاشة بأنّ المخزن المؤقت جاهز للعرض. إنّ المخازن المؤقتة التي يتم استرجاعها من خلال طلب إلى getTargetBuffer() فقط هي الصالحة للاستخدام مع هذا الطلب، ولا يجوز أن يعدّل تطبيق العميل محتوياتBufferDesc. وبعد هذا الطلب، لن يعود المخزن المؤقت صالحًا لاستخدام العميل. يعرض "حسنًا" عند نجاح الإجراء، أو رمز خطأ مناسب يُحتمل أن يتضمّن INVALID_ARG أو BUFFER_NOT_AVAILABLE.

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

يصف هذا القسم السمات الأساسية لشاشة EVS والمطلوبة لتنفيذ EVS. يكون HAL مسؤولاً عن ملء هذه البنية لتحديد شاشة EVS. يمكن أن يكون شاشة حقيقية أو شاشة افتراضية يتم تداخلها أو دمجها مع جهاز عرض آخر.

  • display_id. سلسلة تحدّد الشاشة بشكل فريد يمكن أن يكون هذا هو اسم جهاز kernel للجهاز، أو اسم للجهاز، مثل 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" الواجهة العامة لنظام EVS بهدف جمع عروض الكاميرا الخارجية وعرضها. عندما تسمح برامج تشغيل الأجهزة بواجهة نشطة واحدة فقط لكل مورد (كاميرا أو شاشة)، يسهّل "مدير EVS" الوصول المشترَك إلى الكاميرات. يُعد تطبيق EVS الأساسي أول عميل لجهاز EVS Manager، وهو العميل الوحيد المسموح له بكتابة بيانات العرض (يمكن منح عملاء إضافيين إذن الوصول للقراءة فقط إلى صور الكاميرا).

ينفِّذ "مدير EVS" واجهة برمجة التطبيقات نفسها المستخدَمة في برامج تشغيل HAL الأساسية، ويقدّم خدمة موسّعة من خلال إتاحة استخدام عدّة عملاء متزامنين (يمكن لأكثر من عميل واحد فتح كاميرا من خلال "مدير EVS" وتلقّي بث فيديو).

مخطّط EVS Manager و
EVS Hardware API

الشكل 2: يعكس "مدير EVS" واجهة برمجة التطبيقات الأساسية لأجهزة EVS.

لا تلاحظ التطبيقات أي اختلافات عند تشغيلها من خلال تنفيذ بروتوكول HAL لأجهزة EVS أو واجهة برمجة تطبيقات EVS Manager، باستثناء أنّ EVS Manager API تسمح بالوصول المتزامن إلى بث الكاميرا. مدير EVS هو العميل الوحيد المسموح له باستخدام طبقة HAL لأجهزة EVS، ويعمل كوكيل لطبقة HAL لأجهزة EVS.

تصف الأقسام التالية فقط تلك المكالمات التي لها سلوك مختلف (موسَّع) في عملية تنفيذ "مدير EVS"، وتكون المكالمات المتبقية متطابقة مع أوصاف HAL الخاصة بخدمة EVS.

معادلة IEvsEnumerator

openCamera(string camera_id) generates (IEvsCamera camera);

تحصل على عنصر واجهة يُستخدَم للتفاعل مع كاميرا معيّنة يتم تحديدها من خلال السلسلة الفريدة camera_id. تعرِض قيمة فارغة في حال تعذّر إكمالها. في طبقة "مدير EVS"، طالما توفّرت موارد نظام كافية، يمكن إعادة فتح كاميرا مفتوحة من خلال عملية أخرى، ما يسمح بربط بث الفيديو بتطبيقات المستهلكين المتعدّدة. سلاسل camera_id في طبقة "مدير EVS" هي نفسها سلاسل camera_id التي يتم الإبلاغ عنها في طبقة "أجهزة EVS".

IEvsCamera

قدّم مدير EVS تطبيق IEvs Camera افتراضيًا داخليًا كي لا تؤثر العمليات على الكاميرا التي يُجريها أحد العملاء في العملاء الآخرين، الذين يحتفظون بإمكانية الوصول المستقل إلى كاميراهم.

startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);

بدء عمليات بث الفيديو يمكن للعملاء بدء عمليات بث الفيديو وإيقافها بشكل مستقل على الكاميرا الأساسية نفسها. تبدأ الكاميرا الأساسية عند بدء العميل الأول.

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

لعرض إطار. يجب على كل عميل إعادة إطاراته عند الانتهاء، ولكن يُسمح له بالاحتفاظ بإطاراته طيلة الوقت الذي يرغب فيه. عندما يصل عدد اللقطات التي يحتفظ بها العميل إلى الحد الأقصى الذي تم ضبطه، لن يتلقّى العميل أي لقطات أخرى إلى أن يعرض لقطة. ولا يؤثر تخطّي اللقطات هذا في العملاء الآخرين الذين يواصلون تلقّي كل اللقطات على النحو المتوقّع.

stopVideoStream();

إيقاف بث فيديو يمكن لكل عميل إيقاف بث الفيديو في أي وقت بدون التأثير في العملاء الآخرين. يتم إيقاف بث الكاميرا الأساسي في طبقة الأجهزة عندما يوقف العميل الأخير لكاميرا معيّنة بثها.

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

تُرسل قيمة خاصة ببرنامج التشغيل، ما قد يؤدي إلى تفعيل عميل من التأثير على عميل آخر. لا يمكن لمدير EVS فهم تداعيات كلمات التحكّم التي يحدّدها المورّد، وبالتالي لا تكون افتراضية، وتنطبق أي آثار جانبية على جميع عملاء كاميرا معيّنة. على سبيل المثال، إذا استخدم أحد الموردين هذا الاستدعاء لتغيير عدد اللقطات في الثانية، فسيتلقّى جميع عملاء كاميرا طبقة الأجهزة المتأثرة الإطارات بالمعدل الجديد.

عرض IEvs

يُسمح بمالك واحد فقط للشاشة، حتى على مستوى مدير EVS. لا يضيف المدير أي وظائف، بل يمرر واجهة IEvsDisplay مباشرةً إلى تنفيذ HAL الأساسي.

تطبيق EVS

يتضمّن نظام التشغيل Android تطبيقًا مرجعيًا أصليًا لـ C++ لنظام EVS يتواصل مع "مدير EVS" وHAL للمركبة لتوفير وظائف أساسية لكاميرا الرؤية الخلفية. من المتوقّع أن يبدأ تشغيل التطبيق في وقت مبكر جدًا من عملية تشغيل النظام، مع عرض فيديو مناسب استنادًا إلى الكاميرات المتاحة وحالة السيارة (حالة الترس وإشارة الانعطاف). يمكن للمصنّعين الأصليين للأجهزة تعديل تطبيق EVS أو استبداله بمنطقهم وعرضهم الخاص بالمركبة.

الشكل 3: نموذج منطق تطبيق EVS، الحصول على قائمة الكاميرا



الشكل 4. عينة تطبيق EVS، واستلم معاودة الاتصال بالإطار.

وبما أنّ بيانات الصورة يتم عرضها على التطبيق في ملف تخزين مؤقت عادي للرسومات، يكون التطبيق مسؤولاً عن نقل الصورة من ملف التخزين المؤقت للمصدر إلى ملف التخزين المؤقت للإخراج. على الرغم من أنّ هذا الإجراء يؤدي إلى زيادة تكلفة نسخ البيانات، فإنه يمنح التطبيق أيضًا فرصة عرض الصورة في ملف التخزين المؤقت للعرض بأي طريقة يريدها.

على سبيل المثال، قد يختار التطبيق نقل بيانات البكسل بنفسه، ويُحتمل أن يتم ذلك باستخدام مقياس مضمّن أو عملية تدوير. يمكن للتطبيق أيضًا اختيار استخدام الصورة المصدر كنسيج OpenGL وعرض مشهد معقّد في ذاكرة التخزين المؤقت للإخراج، بما في ذلك العناصر الافتراضية مثل الرموز والتوجيهات والرسوم المتحركة. قد يختار تطبيق أكثر تعقيدًا أيضًا عدة كاميرات إدخال متزامنة ويدمجها في إطار الإخراج الفردي (مثلاً لاستخدامها في عرض افتراضي من الأعلى للأسفل للمناطق المحيطة بالمركبة).

استخدام EGL/SurfaceFlinger في HAL لشاشة EVS

يوضّح هذا القسم كيفية استخدام EGL لعرض تنفيذ HAL لشاشة EVS في Android 10.

يستخدم التطبيق المرجعي لواجهة برمجة التطبيقات EVS HAL واجهة EGL لعرض معاينة الكاميرا على الشاشة ويستخدم libgui لإنشاء سطح عرض EGL المستهدَف. في Android 8 (والإصدارات الأحدث)، يتم تصنيف libgui على أنّه VNDK-private، ويشير ذلك إلى مجموعة من المكتبات المتاحة لمكتبات VNDK والتي لا يمكن لعمليات المورّدين استخدامها. ولأنّ عمليات تنفيذ HAL يجب أن تتوفّر في قسم المورِّد، يتم منع المورّدين من استخدام عمليات تنفيذ سطح المكتب في HAL.

إنشاء libgui لعمليات المورّدين

يُعدّ استخدام libgui هو الخيار الوحيد لاستخدام EGL/SurfaceFlinger في عمليات تنفيذ HAL لشاشة EVS. إنّ الطريقة الأكثر وضوحًا لتنفيذ 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 الذي يزيل كلمة واحدة بسعة 32 بت من بيانات الحزمة. ولأنّ 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

استخدام أداة الربط في عملية تنفيذ HAL لبروتوكول EVS

في Android 8 (والإصدارات الأحدث)، أصبحت عقدة الجهاز /dev/binder حصرية لعمليات الإطار الأساسي، وبالتالي لا يمكن لعمليات المورّدين الوصول إليها. بدلاً من ذلك، يجب أن تستخدم عمليات المورّدين /dev/hwbinder وأن تحوّل أي واجهات AIDL إلى HIDL. بالنسبة إلى المستخدمين الذين يريدون مواصلة استخدام واجهات AIDL بين عمليات المورّدين، استخدِم نطاق الربط /dev/vndbinder.

مجال IPC الوصف
/dev/binder IPC بين عمليات التطبيق/إطار العمل من خلال واجهات AIDL
/dev/hwbinder IPC بين عمليات إطار العمل/المورّد من خلال واجهات HIDL
IPC بين عمليات الموردين باستخدام واجهات HIDL
/dev/vndbinder واجهة برمجة التطبيقات بين عمليات المورّد/المورّد باستخدام واجهات AIDL

في حين أنّ SurfaceFlinger تحدِّد واجهات AIDL، يمكن لعمليات المورّدين استخدام واجهات HIDL فقط للتواصل مع عمليات إطار العمل. يتطلّب تحويل واجهات AIDL الحالية إلى HIDL قدرًا كبيرًا من العمل. لحسن الحظ، يقدّم Android طريقة لاختيار ملف تعريف الارتباط برنامج تشغيل libbinder، الذي يتم ربط عمليات مكتبة مساحة المستخدم به.

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

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


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


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

ملاحظة: يجب أن تستدعي عمليات المورّدين هذا الإجراء قبل الاتصال بأحد Process أو IPCThreadState، أو قبل إجراء أيّ مكالمات مع رابط.

سياسات SELinux

إذا كان تنفيذ الجهاز متوفّرًا بالكامل، يمنع SELinux عمليات المورّد من استخدام /dev/binder. على سبيل المثال، يتم منح تنفيذ نموذج HAL لنظام EVS للنطاق hal_evs_driver ويتطلب أذونات القراءة/الكتابة للنطاق binder_device.

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

ومع ذلك، تؤدي إضافة هذه الأذونات إلى تعذُّر إنشاء التطبيق لأنّها تنتهك قواعد neverallow التالية المحدّدة في system/sepolicy/domain.te لجهاز مزوّد بتقنية الترابيل الكاملة.

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

binder_in_vendor_violators هي سمة مقدَّمة لرصد خطأ وتوجيه عملية التطوير. ويمكن استخدامه أيضًا لمعالجة انتهاك Android 10 الموضّح أعلاه.

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

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

إنشاء عملية مرجعية لتنفيذ HAL في EVS بصفتها عملية مورّد

ويمكنك تطبيق التغييرات التالية على "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;