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

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

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

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

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

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

تطبيق EVS

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

EVS Manager

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

واجهة HIDL لخدمة EVS

يتم تحديد نظام 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 المطلوبة من خلال برامج تشغيل الأجهزة الحالية للكاميرا أو الشاشة. قد يكون إعادة استخدام برامج التشغيل مفيدًا، خاصةً بالنسبة إلى برامج تشغيل الشاشة التي قد يتطلّب عرض الصور فيها التنسيق مع سلاسل التعليمات النشطة الأخرى. يتضمّن نظام التشغيل Android 8.0 برنامج تشغيل تجريبي يستند إلى v4l2 (في packages/services/Car/evs/sampleDriver) ويعتمد على النواة لتوفير دعم 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: سلسلة تحدّد بشكل فريد كاميرا معيّنة. يمكن أن يكون اسم جهاز النواة أو اسمًا للجهاز، مثل rearview. يتم اختيار قيمة هذه السلسلة من خلال تنفيذ طبقة تجريد الأجهزة (HAL)، وتستخدمها بشكل غير شفاف الحزمة أعلاه.
  • vendor_flags. طريقة لتمرير معلومات خاصة بالكاميرا بشكل غير شفاف من برنامج التشغيل إلى تطبيق EVS مخصّص، ويتم تمريرها بدون تفسير من برنامج التشغيل إلى تطبيق EVS، ويمكن للتطبيق تجاهلها.

IEvsCamera

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

getCameraInfo() generates (CameraDesc info);

تعرض CameraDesc لهذه الكاميرا.

setMaxFramesInFlight(int32 bufferCount) generates (EvsResult result);

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

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

startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);

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

oneway doneWithFrame(BufferDesc buffer);

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

stopVideoStream();

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

getExtendedInfo(int32 opaqueIdentifier) generates (int32 value);

يطلب معلومات خاصة ببرنامج التشغيل من تنفيذ طبقة HAL. القيم المسموح بها لـ opaqueIdentifier خاصة ببرنامج التشغيل، ولكن قد يؤدي عدم تمرير أي قيمة إلى تعطُّل برنامج التشغيل. يجب أن يعرض برنامج التشغيل القيمة 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 Semi-Planar) وYV12 (YCrCb 4:2:0 Planar) وYUYV (YCrCb 4:2:2 Interleaved) وRGBA (32 bit R:G:B:x) وBGRA (32 bit B:G:R:x). يجب أن يكون التنسيق المحدّد مصدرًا صالحًا لنسيج GL في تنفيذ GLES على النظام الأساسي.

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

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. تعرض هذه السمة دائمًا القيمة OK ما لم تكن الحالة المطلوبة قيمة تعداد غير معروفة، وفي هذه الحالة، يتم عرض INVALID_ARG.

getDisplayState() generates (DisplayState state);

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

getTargetBuffer() generates (handle bufferHandle);

تعرض هذه السمة معرّفًا لمخزن مؤقت للإطارات مرتبط بالشاشة. قد يتم قفل هذه المخزن المؤقت وكتابة البيانات فيه بواسطة البرامج و/أو GL. يجب عرض هذا المخزن المؤقت من خلال استدعاء returnTargetBufferForDisplay() حتى إذا لم يعُد العرض مرئيًا.

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

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

returnTargetBufferForDisplay(handle bufferHandle) generates (EvsResult result);

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

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

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

  • display_id: سلسلة تحدّد الشاشة بشكل فريد. يمكن أن يكون هذا الاسم هو اسم جهاز النواة أو اسم الجهاز، مثل rearview. يتم اختيار قيمة هذه السلسلة من خلال تنفيذ طبقة تجريد الأجهزة (HAL) ويتم استخدامها بشكل غير شفاف من خلال الحزمة أعلاه.
  • vendor_flags: طريقة لنقل معلومات خاصة بالكاميرا بشكل غير شفاف من برنامج التشغيل إلى تطبيق EVS مخصّص، ويتم نقلها بدون تفسير من برنامج التشغيل إلى تطبيق EVS، ويمكن تجاهلها.
enum DisplayState : uint32 {
    NOT_OPEN,               // Display has not been opened yet
    NOT_VISIBLE,            // Display is inhibited
    VISIBLE_ON_NEXT_FRAME,  // Will become visible with next frame
    VISIBLE,                // Display is currently active
    DEAD,                   // Display is not available. Interface should be closed
}

تصف هذه السمة حالة شاشة نظام EVS، والتي يمكن أن تكون غير مفعَّلة (أي لا يراها السائق) أو مفعَّلة (أي تعرض صورة للسائق). يتضمّن حالة مؤقتة لا يكون فيها العرض مرئيًا بعد، ولكنّه يكون جاهزًا ليصبح مرئيًا عند عرض إطار الصورة التالي باستخدام طلب returnTargetBufferForDisplay().

EVS Manager

يوفر تطبيق EVS Manager الواجهة العامة لنظام EVS من أجل جمع وعرض مشاهد الكاميرا الخارجية. في حال كانت برامج تشغيل الأجهزة تسمح بواجهة نشطة واحدة فقط لكل مورد (كاميرا أو شاشة عرض)، يسهّل &quot;مدير نظام EVS&quot; الوصول المشترك إلى الكاميرات. تطبيق واحد أساسي لخدمة الفيديو في حالات الطوارئ هو أول عميل لـ &quot;مدير خدمة الفيديو في حالات الطوارئ&quot;، وهو العميل الوحيد المسموح له بكتابة بيانات العرض (يمكن منح العملاء الإضافيين إذن الوصول للقراءة فقط إلى صور الكاميرا).

يستخدم &quot;مدير نظام المركبات الخارجي&quot; واجهة برمجة التطبيقات نفسها التي تستخدمها برامج تشغيل طبقة تجريد الأجهزة الأساسية، كما يوفّر خدمة موسّعة من خلال إتاحة استخدام عدة عملاء متزامنين (يمكن لأكثر من عميل فتح كاميرا من خلال &quot;مدير نظام المركبات الخارجي&quot; وتلقّي بث فيديو).

مخطّط لواجهة برمجة التطبيقات الخاصة بأجهزة EVS وEVS Manager
الشكل 2. تعكس فئة EVS Manager واجهة برمجة التطبيقات الأساسية لأجهزة EVS.

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

توضِّح الأقسام التالية المكالمات التي تتضمّن سلوكًا مختلفًا (موسّعًا) في تنفيذ EVS Manager فقط، أما المكالمات المتبقية فهي مطابقة لأوصاف EVS HAL.

IEvsEnumerator

openCamera(string camera_id) generates (IEvsCamera camera);

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

IEvsCamera

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

startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);

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

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

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

stopVideoStream();

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

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

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

IEvsDisplay

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

تطبيق EVS

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

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


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

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

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

استخدام EGL/SurfaceFlinger في EVS Display HAL

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

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

إنشاء libgui لعمليات البائع

يُعدّ استخدام libgui الخيار الوحيد لاستخدام EGL/SurfaceFlinger في عمليات تنفيذ EVS Display HAL. أسهل طريقة لتنفيذ libgui هي من خلال frameworks/native/libs/gui مباشرةً باستخدام هدف إنشاء إضافي في نص برمجي للإنشاء. هذا الهدف هو نفسه تمامًا الهدف libgui باستثناء إضافة حقلَين:

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

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

ملاحظة: يتم إنشاء استهدافات المورّدين باستخدام الماكرو NO_INPUT، الذي يزيل كلمة واحدة من 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

استخدام binder في تنفيذ EVS HAL

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

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

على الرغم من أنّ SurfaceFlinger يحدّد واجهات AIDL، لا يمكن لموفّري الخدمات استخدام سوى واجهات HIDL للتواصل مع عمليات إطار العمل. يتطلّب تحويل واجهات AIDL الحالية إلى HIDL قدرًا كبيرًا من العمل. لحسن الحظ، يوفّر نظام التشغيل Android طريقة لاختيار برنامج تشغيل Binder الخاص بـ 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

إذا كان تنفيذ الجهاز متوافقًا تمامًا مع Treble، يمنع SELinux عمليات المورّد من استخدام /dev/binder. على سبيل المثال، يتم تعيين نموذج لتنفيذ EVS HAL إلى النطاق 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 لجهاز متوافق مع الإصدار الكامل من Treble.

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

binder_in_vendor_violators هي سمة يتم توفيرها لرصد خطأ وتوجيه عملية التطوير. ويمكن أيضًا استخدامها لحلّ مشكلة انتهاك سياسة 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;