طبقة تجريد الأجهزة (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);

تصدر واجهة IEvs Camera (وهو عكس استدعاء 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، يتخطّى البث اللقطات إلى أن يتم إرجاع ذاكرة تخزين مؤقت لإعادة استخدامها. من القانوني أن يصل هذا الطلب في أي وقت، حتى أثناء تنفيذ عمليات البث، وفي هذه الحالة يجب إضافة أو إزالة وحدات التخزين المؤقت من السلسلة حسب الاقتضاء. إذا لم يتم إجراء أي اتصال لنقطة الدخول هذه، فإن كاميرا IEvs Camera تتيح إطارًا واحدًا على الأقل بشكل تلقائي، ولكن يتم قبول أكثر منها.

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

startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);

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

oneway doneWithFrame(BufferDesc buffer);

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

stopVideoStream();

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

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

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

IEvsDisplay

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

تطبيق EVS

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

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


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

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

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

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

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

إنّ تنفيذ مرجع EGL يستخدم 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

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

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

نطاق IPC الوصف
/dev/binder تبادل البيانات بين عمليات التطبيق أو إطار العمل باستخدام واجهات AIDL
/dev/hwbinder تبادل البيانات بين عمليات إطار العمل/المورّد باستخدام واجهات HIDL
تبادل البيانات بين عمليات المورّد باستخدام واجهات HIDL
/dev/vndbinder IPC بين عمليات البائعين/المورِّدين باستخدام واجهات 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;