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

يتم توفير نموذج لتطبيق EVS بلغة C++‎(/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: سلسلة تحدّد كاميرا معيّنة بشكل فريد. يمكن أن يكون اسم جهاز kernel أو اسمًا للجهاز، مثل 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()، قد يستمر هذا الإجراء أثناء إفراغ خط الأنابيب. يجب أن يتم عرض كل إطار، وعند عرض آخر إطار في البث، سيتم عرض bufferHandle بقيمة NULL، ما يشير إلى نهاية البث وعدم عرض أي إطارات أخرى. لا حاجة إلى إعادة bufferHandle NULL عبر doneWithFrame()، ولكن يجب إرجاع جميع المعرّفات الأخرى.

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

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

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
}

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

EVS Manager

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

IEvsDisplay

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

تطبيق EVS

يتضمّن نظام التشغيل Android عملية تنفيذ مرجعية بلغة C++ لتطبيق EVS يتواصل مع &quot;مدير EVS&quot; و&quot;طبقة تجريد الأجهزة في المركبة&quot; لتوفير وظائف أساسية لكاميرا الرؤية الخلفية. من المتوقّع أن يبدأ التطبيق في وقت مبكر جدًا من عملية تشغيل النظام، مع عرض فيديو مناسب حسب الكاميرات المتاحة وحالة السيارة (حالة ناقل الحركة وإشارات الانعطاف). يمكن لمصنّعي المعدات الأصلية تعديل تطبيق 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، ويشير ذلك إلى مجموعة من المكتبات المتاحة لمكتبات VNDK التي لا يمكن أن تستخدمها عمليات المورّد. بما أنّ عمليات تنفيذ HAL يجب أن تكون في قسم المورّد، يتم منع المورّدين من استخدام Surface في عمليات تنفيذ HAL.

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

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

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

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

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

نطاق IPC الوصف
/dev/binder التواصل البيني للعمليات (IPC) بين عمليات إطار العمل/التطبيق باستخدام واجهات لغة تعريف واجهة نظام Android ‏(AIDL)
/dev/hwbinder التواصل البيني للعمليات بين عمليات الإطار/المورّد باستخدام واجهات HIDL
التواصل البيني للعمليات بين عمليات المورّد باستخدام واجهات 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;