طبقة تجريد الأجهزة (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. من المتوقّع أن يتم تشغيله من خلال init فور توفّر EVS وCar Service، ويُستهدف تشغيله خلال ثانيتَين (2) من تشغيل الجهاز. يمكن لمصنّعي المعدّات الأصلية تعديل تطبيق EVS أو استبداله حسب الرغبة.

مدير EVS

يقدّم "مدير EVS" (/packages/services/Car/evs/manager) العناصر الأساسية التي يحتاجها تطبيق EVS لتنفيذ أيّ شيء، بدءًا من عرض كاميرا الرؤية الخلفية البسيطة ووصولاً إلى عرض متعدّد الكاميرات بتقنية 6 درجات من الحرية. يتم عرض واجهته من خلال HIDL وهي مصمّمة لقبول عملاء متعدّدين متزامنين. يمكن للتطبيقات والخدمات الأخرى (على وجه التحديد "خدمة السيارة") طلب معلومات عن حالة "مدير EVS" لمعرفة الحالات التي يكون فيها نظام 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. تعرِض قيمة فارغة في حال تعذّر إكمالها. لا يمكن أن تؤدي محاولات إعادة فتح كاميرا مفتوحة إلى تعذّر فتحها. لتجنُّب حالات التداخل المرتبطة ببدء تشغيل التطبيق وإغلاقه، يجب أن يؤدي إعادة فتح كاميرا إلى إغلاق المثيل السابق حتى يمكن تنفيذ الطلب الجديد. يجب وضع مثيل الكاميرا الذي تم الاستيلاء عليه بهذه الطريقة في حالة غير نشطة، في انتظار التدمير النهائي والاستجابة لأي طلب للتأثير في حالة الكاميرا باستخدام رمز الإرجاع 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 استخدام هذه الدعوة في حالة تلقائية. إذا كان السائق يتعرّف على القيم ويقبلها، من المفترض أن يتم عرض الرسالة "حسنًا"، وإلا يجب عرض 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 علامات HAL ذات الصلة).
  • 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 API باستثناء أنّ 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

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

startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);

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

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

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

stopVideoStream();

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

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

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

IEvsDisplay

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

تطبيق EVS

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

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



الشكل 4: نموذج منطق تطبيق EVS، تلقّي callback الإطار

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

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

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

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

يستخدم التطبيق المرجعي لواجهة برمجة التطبيقات EVS HAL واجهة EGL لعرض معاينة الكاميرا على الشاشة ويستخدم libgui لإنشاء سطح عرض EGL المستهدَف. في Android 8 (والإصدارات الأحدث)، يتم تصنيف libgui على أنّه VNDK-private، ويشير ذلك إلى مجموعة من المكتبات المتاحة لمكتبات VNDK والتي لا يمكن لعمليات المورّدين استخدامها. وبما أنّ عمليات تنفيذ HAL يجب أن تكون موجودة في قسم المورّد، يتم منع المورّدين من استخدام Surface في عمليات تنفيذ 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 تبادل البيانات بين عمليات التطبيق أو إطار العمل باستخدام واجهات AIDL
/dev/hwbinder معالجة البيانات بين عمليات إطار العمل/المورّد باستخدام واجهات HIDL
معالجة البيانات بين عمليات المورّد باستخدام واجهات 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;