كاميرا مركبة HAL

يحتوي Android على طبقة تجريد أجهزة HIDL (HAL) الخاصة بالسيارات والتي توفر إمكانية التقاط الصور وعرضها في وقت مبكر جدًا من عملية تمهيد 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. من المتوقع أن يتم تشغيله بواسطة init بمجرد توفر EVS وخدمة السيارات، ويتم استهدافهما خلال ثانيتين (2) من التشغيل. يمكن لمصنعي المعدات الأصلية تعديل تطبيق EVS أو استبداله حسب الرغبة.

مدير EVS

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

واجهة EVS HIDL

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

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

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

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

وصف واجهة أجهزة EVS

يصف القسم HAL. من المتوقع أن يقدم البائعون تطبيقات لواجهة برمجة التطبيقات (API) هذه التي تم تكييفها لتناسب أجهزتهم.

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 . سلسلة تحدد كاميرا معينة بشكل فريد. يمكن أن يكون اسم الجهاز النواة للجهاز أو اسم الجهاز مثل الرؤية الخلفية . يتم اختيار قيمة هذه السلسلة بواسطة تطبيق 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 إطارات في الثانية. يتم احتساب الوقت اللازم لبدء دفق الفيديو بفعالية مقابل أي متطلبات زمنية لبدء تشغيل كاميرا الرؤية الخلفية. إذا لم يبدأ الدفق، فيجب إرجاع رمز الخطأ؛ وإلا يتم إرجاع موافق.

oneway doneWithFrame(BufferDesc buffer);

إرجاع الإطار الذي تم تسليمه إلى IEvsCameraStream. عند الانتهاء من استهلاك إطار تم تسليمه إلى واجهة IEvsCameraStream، يجب إرجاع الإطار إلى IEvsCamera لإعادة استخدامه. يتوفر عدد صغير ومحدود من المخازن المؤقتة (ربما صغير مثل واحد)، وإذا تم استنفاد العرض، فلن يتم تسليم أي إطارات أخرى حتى يتم إرجاع المخزن المؤقت، مما قد يؤدي إلى تخطي الإطارات (يشير المخزن المؤقت بمقبض فارغ إلى النهاية) من الدفق ولا يلزم إعادته من خلال هذه الوظيفة). يُرجع موافق عند النجاح، أو قد يتضمن رمز الخطأ المناسب 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، مما يدل على نهاية الدفق وعدم حدوث أي عمليات تسليم إطار أخرى. لا يلزم إرسال NULL bufferHandle نفسه مرة أخرى عبر doneWithFrame() ، ولكن يجب إرجاع كافة المقابض الأخرى

في حين أن تنسيقات المخزن المؤقت الخاصة ممكنة من الناحية الفنية، فإن اختبار التوافق يتطلب أن يكون المخزن المؤقت في أحد التنسيقات الأربعة المدعومة: NV21 (YCrCb 4:2:0 Semi-Planar)، YV12 (YCrCb 4:2:0 Planar)، 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 . تُرجع الدالة 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 معشق)، RGBA (32 بت R:G:B:x)، BGRA (32 بت 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، وهو العميل الوحيد المسموح له بكتابة بيانات العرض (يمكن منح العملاء الإضافيين حق الوصول للقراءة فقط إلى صور الكاميرا).

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

مدير EVS ومخطط API لأجهزة EVS.

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

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

تصف الأقسام التالية فقط تلك المكالمات التي لها سلوك (موسع) مختلف في تطبيق EVS Manager؛ المكالمات المتبقية مطابقة لأوصاف EVS HAL.

IEvsEnumerator

openCamera(string camera_id) generates (IEvsCamera camera);

يحصل على كائن واجهة يستخدم للتفاعل مع كاميرا معينة تم تحديدها بواسطة سلسلة Camera_id الفريدة. إرجاع NULL عند الفشل. في طبقة مدير EVS، طالما تتوفر موارد نظام كافية، قد يتم فتح الكاميرا المفتوحة بالفعل مرة أخرى من خلال عملية أخرى، مما يسمح بنقل دفق الفيديو إلى تطبيقات المستهلك المتعددة. سلاسل camera_id في طبقة EVS Manager هي نفس تلك التي تم الإبلاغ عنها إلى طبقة أجهزة EVS.

IEvsCamera

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

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 وVehicle HAL لتوفير وظائف كاميرا الرؤية الخلفية الأساسية. من المتوقع أن يبدأ التطبيق مبكرًا جدًا في عملية تشغيل النظام، مع عرض مقطع فيديو مناسب وفقًا للكاميرات المتاحة وحالة السيارة (حالة إشارة الانعطاف والترس). يمكن لمصنعي المعدات الأصلية تعديل تطبيق EVS أو استبداله بالمنطق والعرض التقديمي الخاصين بالمركبة.

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



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

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

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

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

يشرح هذا القسم كيفية استخدام EGL لتقديم تطبيق EVS Display HAL في Android 10.

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

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

إنشاء تطبيق مرجعي لـ EVS HAL كعملية بائع

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