يحتوي Android على طبقة HIDL Hardware Abstraction Layer (HAL) للسيارات التي توفّر إمكانية التقاط الصور وعرضها في مرحلة مبكرة جدًا من عملية تشغيل Android وتستمر في العمل طوال مدة استخدام النظام. يتضمّن HAL حِزمة نظام العرض الخارجي (EVS) ويُستخدَم عادةً لإتاحة كاميرا الرؤية الخلفي ومشاهد العرض المحيطي في المركبات المزوّدة بأنظمة تكامل الترفيه والمعلومات (IVI) في المركبات والمستندة إلى نظام التشغيل Android. تتيح تقنية EVS أيضًا تنفيذ ميزات متقدّمة في تطبيقات المستخدمين.
يتضمّن Android أيضًا واجهة برنامج تشغيل العرض والتقاط الصور الخاصة بـ EVS (في تطبيق /hardware/interfaces/automotive/evs/1.0
). وعلى الرغم من أنّه من الممكن
إنشاء تطبيق كاميرا للرؤية الخلفية فوق خدمات العرض والكاميرا الحالية التي تعمل بنظام التشغيل Android، من المحتمل أن يتأخر تشغيل هذا التطبيق
في عملية تشغيل Android. يتيح استخدام HAL مخصّص واجهة سلسة ويوضّح ما يحتاج إليه المصنّع الأصلي للجهاز لتوفير حِزمة EVS.
مكونات النظام
يتضمّن نظام EVS مكونات النظام التالية:
تطبيق EVS
يُعدّ تطبيق EVS لنظام التشغيل C++
(/packages/services/Car/evs/app
) نموذجاً لتنفيذ
المراجع. يتولّى هذا التطبيق طلب لقطات الفيديو من
مدير EVS وإرسال اللقطات المكتملة للعرض مرة أخرى إلى مدير EVS.
من المتوقّع أن يتم تشغيله من خلال init فور توفّر EVS وCar Service، ويُستهدف تشغيله خلال ثانيتَين (2) من تشغيل الجهاز. يمكن لمصنّعي المعدّات الأصلية تعديل تطبيق EVS
أو استبداله حسب الرغبة.
مدير EVS
يقدّم "مدير EVS" (/packages/services/Car/evs/manager
)
العناصر الأساسية التي يحتاجها تطبيق EVS لتنفيذ أيّ شيء، بدءًا من
عرض كاميرا الرؤية الخلفية البسيطة ووصولاً إلى عرض متعدّد الكاميرات بتقنية 6 درجات من الحرية. يتم عرض واجهته
من خلال HIDL وهي مصمّمة لقبول عملاء متعدّدين متزامنين.
يمكن للتطبيقات والخدمات الأخرى (على وجه التحديد خدمة السيارة) الاستعلام عن حالة "مدير EVS" لمعرفة الوقت الذي يكون فيه نظام EVS نشطًا.
واجهة EVS HIDL
يتم تحديد نظام EVS، الذي يشمل الكاميرا وعناصر العرض، في
حزمة "android.hardware.automotive.evs
". يتم في /hardware/interfaces/automotive/evs/1.0/default
تقديم نموذج تنفيذي يمارس الواجهة (إنشاء صور اختبار اصطناعية ويتأكّد من أنّ الصور تتيح الانتقال ذهابًا وإيابًا).
المصنّع الأصلي للجهاز مسؤول عن تنفيذ واجهة برمجة التطبيقات التي يتم التعبير عنها من خلال ملفات .hal
في /hardware/interfaces/automotive/evs
. وتتحمّل عمليات التنفيذ هذه
مسؤولية ضبط البيانات من الكاميرات الفعلية وجمعها
وإرسالها من خلال وحدات تخزين مؤقت للذاكرة المشتركة يمكن لـ Gralloc التعرّف عليها. إنّ جانب الشاشة
من التنفيذ مسؤول عن توفير ذاكرة تخزين مؤقتة مشترَكة
يمكن للتطبيق ملؤها (عادةً باستخدام معالجة EGL) وعرض
اللقطات المكتملة بدلاً من أي شيء آخر قد تريد الظهور على
الشاشة الفعلية. قد يتم تخزين عمليات تنفيذ واجهة EVS التي يجريها المورّدون
بموجب /vendor/… /device/…
أو hardware/…
(على سبيل المثال،
/hardware/[vendor]/[platform]/evs
).
برامج تشغيل النواة
يتطلب الجهاز المتوافق مع حِزمة EVS برامج تشغيل نواة. بدلاً من
إنشاء برامج تشغيل جديدة، يمكن للمصنعين الأصليين للأجهزة تفعيل الميزات المطلوبة لبروتوكول EVS من خلال
برامج تشغيل الأجهزة الحالية للكاميرا أو الشاشة. يمكن أن تكون إعادة استخدام برامج التشغيل مفيدة، خاصةً لبرامج تشغيل العرض التي قد تتطلب عرض الصور التنسيق مع سلاسل التعليمات النشطة الأخرى. يتضمّن الإصدار 8.0 من نظام التشغيل Android ملفًا نموذجيًا لبرنامج تشغيل يستند إلى v4l2 (في packages/services/Car/evs/sampleDriver
) ويعتمد على kernel لإتاحة v4l2 وعلى SurfaceFlinger لعرض
الصورة الناتجة.
وصف واجهة جهاز EVS
يصف القسم HAL. يُتوقع من البائعين تقديم عمليات تنفيذ واجهة برمجة التطبيقات هذه لتتناسب مع أجهزتهم.
IEvsEnumerator
هذا العنصر مسؤول عن سرد أجهزة EVS المتاحة في النظام (كاميرا واحدة أو أكثر وجهاز عرض واحد).
getCameraList() generates (vec<CameraDesc> cameras);
تعرِض هذه السمة متجهًا يحتوي على أوصاف لجميع الكاميرات في النظام. والافتراض أن مجموعة الكاميرات ثابتة ويمكن معرفتها في وقت التشغيل. للحصول على تفاصيل حول أوصاف الكاميرا، يُرجى الاطّلاع على CameraDesc
.
openCamera(string camera_id) generates (IEvsCamera camera);
تحصل على عنصر واجهة يُستخدَم للتفاعل مع كاميرا معيّنة
يتم تحديدها من خلال السلسلة الفريدة camera_id. تعرِض قيمة فارغة في حال تعذّر إكمالها.
ولن تنجح محاولات إعادة فتح كاميرا مفتوحة. لتجنُّب شروط السباق
المرتبطة بتشغيل التطبيق وإيقافه، يجب أن تؤدي إعادة فتح الكاميرا
إلى إيقاف المثيل السابق حتى يتم تنفيذ الطلب الجديد. يجب وضع مثيل الكاميرا الذي تم منعه بهذه الطريقة في حالة غير نشطة، مع انتظار التلف النهائي له والاستجابة لأي طلب للتأثير في حالة الكاميرا برمز إرجاع OWNERSHIP_LOST
.
closeCamera(IEvsCamera camera);
يُطلق واجهة IEvsCamera (وهو عكس طلب
openCamera()
). يجب أولاً
إيقاف بث الفيديو من الكاميرا من خلال الاتصال برقم stopVideoStream()
قبل الاتصال برقم closeCamera
.
openDisplay() generates (IEvsDisplay display);
الحصول على كائن واجهة يُستخدَم للتفاعل حصريًا مع شاشة EVS الخاصة بالنظام. يمكن لعميل واحد فقط الاحتفاظ بمثيل وظيفي من IEvsDisplay في كل
وقت. على غرار سلوك الفتح العدواني الموضّح في openCamera
،
قد يتم إنشاء عنصر IEvsDisplay جديد في أي وقت ويوقف أي مثيلات سابقة. تظل النُسخ التي تم إلغاء صلاحيتها موجودة وتستجيب لطلبات تشغيل الدوالّ
من مالكيها، ولكن يجب ألا تُجري أي عمليات تغيُّر عندما تكون غير نشطة. في نهاية المطاف،
من المتوقّع أن يلاحظ تطبيق العميل رموز OWNERSHIP_LOST
خطأ
المعروضة ويغلق الواجهة غير النشطة ويُطلق سراحها.
closeDisplay(IEvsDisplay display);
تصدر واجهة IEvsDisplay (وهي عكس استدعاء
openDisplay()
). يجب إعادة الوسائط المخزّنة مؤقتًا التي تم استلامها مع مكالمات getTargetBuffer()
إلى الشاشة قبل
إغلاق الشاشة.
getDisplayState() generates (DisplayState state);
الحصول على حالة العرض الحالية من المفترض أن يُبلغ تنفيذ HAL عن الحالة الراهنة
الحالية، والتي قد تختلف عن الحالة المطلوبة مؤخرًا.
يجب أن يقع المنطق المسؤول عن تغيير حالات العرض فوق طبقة الجهاز، ما يجعل من غير المستحسن أن يؤدي تطبيق HAL إلى تغيير حالات العرض تلقائيًا. إذا لم يكن أي عميل يعرض الشاشة حاليًا (من خلال طلب برمجي لدالّة
openDisplay)، ستعرض هذه الدالة القيمة NOT_OPEN
. في الحالات الأخرى، ستبلغ الصفحة عن الحالة الحالية لشاشة EVS (يمكنك الاطّلاع على
IEvsDisplay API).
struct CameraDesc { string camera_id; int32 vendor_flags; // Opaque value }
camera_id
: سلسلة تُحدِّد كاميرا معيّنة بشكل فريد يمكن أن يكون اسم جهاز kernel للجهاز أو اسمًا للجهاز، مثل rearview. يتم اختيار قيمة هذه السلسلة من خلال تنفيذ HAL ويتم استخدامها بشكل غير شفاف من خلال الحزمة أعلاه.vendor_flags
. طريقة لنقل معلومات كاميرا متخصصة بشكل غير واضح من برنامج التشغيل إلى تطبيق EVS مخصّص. ويتم نقلها بدون تفسير من برنامج التشغيل إلى تطبيق EVS الذي يمكنه تجاهلها.
IEvsCamera
يمثل هذا الكائن كاميرا واحدة وهو الواجهة الأساسية لالتقاط الصور.
getCameraInfo() generates (CameraDesc info);
عرض CameraDesc
من هذه الكاميرا
setMaxFramesInFlight(int32 bufferCount) generates (EvsResult result);
يحدِّد عمق سلسلة المخزن المؤقت المطلوب من الكاميرا أن تتوافق معه. يمكن أن يحتفظ عميل IEvsCamera بحدٍ أقصى من
هذا العدد من اللقطات في الوقت نفسه. إذا تم تسليم
هذا العدد الكبير من اللقطات إلى المُستلِم بدون إرجاعها من قِبل
doneWithFrame
، يتخطّى البث اللقطات إلى أن يتم إرجاع ذاكرة تخزين مؤقت
لإعادة استخدامها. من القانوني أن ترد هذه المكالمة في أي وقت، حتى إذا كانت عمليات البث قيد التشغيل، وفي هذه الحالة يجب إضافة الموارد الاحتياطية أو إزالتها من السلسلة
حسب الحاجة. في حال عدم إجراء أي مكالمة إلى نقطة الدخول هذه، تتيح IEvsCamera
لقطة واحدة على الأقل تلقائيًا، مع قبول المزيد.
إذا تعذّر استيفاء عدد الفواصل المطلوبة، تُرجع الدالة
BUFFER_NOT_AVAILABLE
أو رمز خطأ آخر ذي صلة. في هذه الحالة،
يواصل النظام العمل بالقيمة التي تم ضبطها سابقًا.
startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);
يطلب إرسال لقطات كاميرا EVS من هذه الكاميرا. يبدأ IEvsCameraStream
بتلقّي مكالمات دورية تتضمّن إطارات صور جديدة إلى أن يتم استدعاء
stopVideoStream()
. يجب أن يبدأ عرض اللقطات
في غضون 500 ملي ثانية من طلب startVideoStream
، وبعد البدء، يجب
أن يتم إنشاؤها بمعدّل 10 لقطات في الثانية على الأقل. ويتم احتساب الوقت اللازم لبدء بث الفيديو بشكل فعّال
من أي متطلبات متعلقة بوقت بدء تشغيل كاميرا الرؤية الخلفية. إذا لم يتم بدء البث، يجب عرض رمز خطأ، وإلا يتم عرض رسالة OK.
oneway doneWithFrame(BufferDesc buffer);
عرض إطار تم إرساله إلى IEvsCameraStream عند الانتهاء من استخدام إطار تم إرساله إلى واجهة IEvsCameraStream، يجب إعادة الإطار إلى IEvsCamera لإعادة استخدامه. يتوفّر عدد صغير ومحدّد من ملفّات التخزين المؤقت (قد يكون عددًا صغيرًا مثل ملف واحد)، وإذا نفد هذا العدد، لن يتم إرسال أي ملفّات
أخرى إلى أن يتم عرض ملف تخزين مؤقت، ما قد يؤدي إلى عدم عرض
بعض اللقطات (يشير ملف التخزين المؤقت الذي يحتوي على معرّف فارغ إلى نهاية البث ولا
يلزم إرجاعه من خلال هذه الدالة). يتم عرض OK في حال النجاح، أو
رمز الخطأ المناسب الذي قد يتضمّن INVALID_ARG
أو
BUFFER_NOT_AVAILABLE
.
stopVideoStream();
يوقف إرسال إطارات كاميرا EVS. وبما أنّ عملية الإرسال غير متزامنة، قد يستمر وصول
اللقطات لبعض الوقت بعد ظهور نتيجة هذا الطلب. يجب إرجاع كل إطار
إلى أن يتم إرسال إشارة إغلاق البث إلى IDEvsCameraStream. يُسمح باستدعاء stopVideoStream
في بث
سبق إيقافه أو لم يبدأ أبدًا، وفي هذه الحالات يتم تجاهله.
getExtendedInfo(int32 opaqueIdentifier) generates (int32 value);
يطلب معلومات خاصة بالسائق من تنفيذ HAL. القيم
المسموح بها لـ opaqueIdentifier
خاصة ببرنامج التشغيل، ولكن قد لا تؤدي أي قيمة تم تمريرها إلى تعطُّل برنامج التشغيل. يجب أن يعرض برنامج تشغيل الجهاز القيمة 0 لأي opaqueIdentifier
غير معروف.
setExtendedInfo(int32 opaqueIdentifier, int32 opaqueValue) generates (EvsResult result);
تُرسِل قيمة خاصة بالسائق إلى تنفيذ HAL. لا يتم توفير هذه الإضافة إلا لتسهيل استخدام الإضافات المتعلّقة بالمركبات، ولا يُفترض أن يتطلّب تنفيذ HAL استخدام هذه الدعوة في حالة تلقائية. إذا كان
السائق يتعرّف على القيم ويقبلها، من المفترض أن يتم عرض الرمز "حسنًا"، وإلا يجب عرض الرمزINVALID_ARG
أو رمز خطأ تمثيلي آخر.
struct BufferDesc { uint32 width; // Units of pixels uint32 height; // Units of pixels uint32 stride; // Units of pixels uint32 pixelSize; // Size of single pixel in bytes uint32 format; // May contain values from android_pixel_format_t uint32 usage; // May contain values from Gralloc.h uint32 bufferId; // Opaque value handle memHandle; // gralloc memory buffer handle }
يصف صورة تم تمريرها من خلال واجهة برمجة التطبيقات. يكون محرك HAL مسؤولاً عن
ملء هذه البنية لوصف ذاكرة التخزين المؤقت للصورة، ويجب أن يتعامل العميل HAL
مع هذه البنية على أنّها للقراءة فقط. تحتوي الحقول على معلومات كافية للسماح للعميل بإعادة إنشاء كائن ANativeWindowBuffer
،
كما قد يكون مطلوبًا لاستخدام الصورة مع EGL مع
الإضافة eglCreateImageKHR()
.
width
. العرض بالبكسل للصورة المعروضة.height
. الارتفاع بالبكسل للصورة المعروضة.stride
. عدد وحدات البكسل التي يشغلها كل صف في الذاكرة، مع مراعاة أي مساحة فارغة لمحاذاة الصفوف يتم التعبير عنها بالبكسل لمطابقة المعيار الذي اتّبعه gralloc لوصف المخزن المؤقت.pixelSize
: عدد البايتات التي يشغلها كل بكسل فردي، مما يتيح احتساب الحجم بالبايتات اللازم للانتقال بين الصفوف في الصورة (stride
بايت =stride
بكسل *pixelSize
).format
: تنسيق البكسل المستخدَم في الصورة يجب أن يكون التنسيق المقدَّم متوافقًا مع تنفيذ OpenGL في النظام الأساسي. لاجتياز اختبارات التوافق، يجب استخدامHAL_PIXEL_FORMAT_YCRCB_420_SP
عند استخدام الكاميرا، وRGBA
أوBGRA
عند استخدام الشاشة.usage
. علامات الاستخدام التي تم ضبطها من خلال تنفيذ HAL من المتوقّع أن يرسل عملاء HAL هذه العناصر بدون تعديل (للحصول على التفاصيل، يُرجى الرجوع إلىGralloc.h
العلامات ذات الصلة).bufferId
. قيمة فريدة يحدّدها تنفيذ HAL للسماح بالتعرّف على مخزن مؤقت بعد رحلة ذهاب وإياب من خلال واجهات برمجة تطبيقات HAL ويمكن اختيار القيمة المخزنة في هذا الحقل عشوائيًا من خلال تنفيذ HAL.memHandle
. معرّف وحدة تخزين مؤقت للذاكرة الأساسية التي تحتوي على بيانات الصورة. قد يختار تنفيذ HAL تخزين مؤشر المخزن المؤقت Gralloc هنا.
IEvsCameraStream
ينفِّذ العميل هذه الواجهة لتلقّي عمليات إرسال إطارات الفيديو غير المتزامنة.
deliverFrame(BufferDesc buffer);
تتلقّى طلبات من HAL في كل مرة يصبح فيها إطار فيديو جاهزًا للفحص.
يجب إرجاع معالِم التخزين المؤقت الذي تتلقّاه هذه الطريقة من خلال طلبات إلى
IEvsCamera::doneWithFrame()
. عند إيقاف بث الفيديو من خلال
طلب إلى IEvsCamera::stopVideoStream()
، قد يستمر هذا الطلب للرجوع
إلى أن يتم تفريغ مسار الإرسال. يجب أن يستمر عرض كل لقطة. وعند تسليم اللقطة الأخيرة
في البث، يتم تسليم قيمة NULL bufferHandle
،
ما يشير إلى نهاية البث ولا يتم تسليم المزيد من اللقطات. لا يلزم إعادة إرسال القيمة NULL
bufferHandle
نفسها مع
doneWithFrame()
، ولكن يجب إعادة إرسال جميع الأسماء المعرِّفة الأخرى.
على الرغم من أنّ تنسيقات المخزن المؤقت التي تملكها جهة خارجية ممكنة من الناحية الفنية، يتطلّب اختبار التوافق أن يكون المخزن المؤقت بأحد التنسيقات الأربعة المتوافقة: NV21 (YCrCb 4:2:0 شبه مسطّح)، وYV12 (YCrCb 4:2:0 مسطّح)، وYUYV (YCrCb 4:2:2 مُدرَج)، وRGBA (32 بت R:G:B:x)، وBGRA (32 بت B:G:R:x). يجب أن يكون التنسيق المحدّد مصدرًا صالحًا لنسيج GL في عملية تنفيذ GLES على المنصة.
يجب عدم اعتماد التطبيق على أي مطابقة
بين الحقل bufferId
وmemHandle
في
بنية BufferDesc
. إنّ قيم bufferId
هي
خاصة بشكل أساسي بتنفيذ برنامج تشغيل HAL، وقد يستخدمه (ويعيد استخدامه)
حسبما يراه مناسبًا.
عرض IEvs
يمثّل هذا الكائن شاشة Evs، ويتحكّم في حالة الشاشة، ويعالج العرض الفعلي للصور.
getDisplayInfo() generates (DisplayDesc info);
تعرِض هذه السمة معلومات أساسية عن شاشة EVS التي يوفّرها النظام (راجِع DisplayDesc).
setDisplayState(DisplayState state) generates (EvsResult result);
لضبط حالة العرض يمكن للعملاء ضبط حالة العرض للتعبير عن الحالة المطلوبة، ويجب أن يقبل تنفيذ HAL طلبًا بشأن أي حالة أثناء التواجد في أي حالة أخرى، على الرغم من أنّ الاستجابة قد تكون تجاهل الطلب.
عند بدء التشغيل، يتم ضبط الشاشة لتبدأ في حالة
NOT_VISIBLE
، وبعد ذلك من المتوقّع أن يطلب العميل
حالة VISIBLE_ON_NEXT_FRAME
ويبدأ بعرض الفيديو. عندما لا يعود مطلوبًا عرض المحتوى، من المتوقّع أن يطلب العميل حالة
NOT_VISIBLE
بعد مرور آخر إطار من الفيديو.
ويمكن طلب أي ولاية في أي وقت. إذا كانت الشاشة
معروضة حاليًا، من المفترض أن تظل معروضة إذا تم ضبطها على
VISIBLE_ON_NEXT_FRAME
. يتم دائمًا عرض القيمة "حسنًا" ما لم تكن الحالة المطلوبة
هي قيمة قائمة أرقام صحيحة غير معروفة، وفي هذه الحالة يتم عرض القيمةINVALID_ARG
.
getDisplayState() generates (DisplayState state);
الحصول على حالة العرض من المفترض أن يُبلغ تنفيذ HAL عن الحالة الحالية الفعلية، والتي قد تختلف عن الحالة المطلوبة مؤخرًا. يجب أن يكون المنطق المسؤول عن تغيير حالات العرض أعلى ملف برمجي الجهاز، ما يجعل من غير المرغوب فيه أن يؤدي تنفيذ HAL إلى تغيير حالات العرض بشكل عفوي.
getTargetBuffer() generates (handle bufferHandle);
لعرض مؤشر للمخزن المؤقت للإطارات المرتبط بالشاشة. قد يتم قفل هذا المخزن المؤقت
والكتابة إليه بواسطة البرنامج و/أو GL. يجب إرجاع هذا المخزن المؤقت
من خلال استدعاء returnTargetBufferForDisplay()
حتى إذا لم يكن الشاشة
مرئية.
على الرغم من أنّ تنسيقات المخزن المؤقت التي تملكها جهة خارجية ممكنة من الناحية التقنية، يتطلّب اختبار التوافق أن يكون المخزن المؤقت بأحد التنسيقات الأربعة المتوافقة: NV21 (YCrCb 4:2:0 Semi-Planar) أو YV12 (YCrCb 4:2:0 Planar) أو YUYV (YCrCb 4:2:2 Interleaved) أو RGBA (32 bit R:G:B:x) أو BGRA (32 bit B:G:R:x). يجب أن يكون التنسيق المحدد هدف عرض GL صالح على تنفيذ GLES للنظام الأساسي.
في حال حدوث خطأ، يتم إرجاع مخزن مؤقت يتضمّن معرّفًا غير صالح، ولكن لا ينبغي
إرسال هذا المخزن المؤقت مرة أخرى إلى returnTargetBufferForDisplay
.
returnTargetBufferForDisplay(handle bufferHandle) generates (EvsResult result);
تخبر الشاشة بأنّ المخزن المؤقت جاهز للعرض. إنّ المخازن المؤقتة التي يتم استرجاعها
من خلال طلب إلى getTargetBuffer()
فقط هي الصالحة للاستخدام مع
هذا الطلب، ولا يجوز أن يعدّل تطبيق العميل محتوياتBufferDesc
. وبعد هذا الطلب، لن يعود المخزن المؤقت صالحًا لاستخدام العميل. تعرِض هذه الدالة القيمة "حسنًا" في حال النجاح، أو رمز الخطأ المناسب الذي قد يشمل INVALID_ARG
أو BUFFER_NOT_AVAILABLE
.
struct DisplayDesc { string display_id; int32 vendor_flags; // Opaque value }
يصف هذا القسم السمات الأساسية لشاشة EVS والمطلوبة لتنفيذ EVS. يكون HAL مسؤولاً عن ملء هذه البنية لتحديد شاشة EVS. يمكن أن يكون شاشة حقيقية أو شاشة افتراضية يتم تداخلها أو دمجها مع جهاز عرض آخر.
display_id
. سلسلة تحدّد الشاشة بشكل فريد يمكن أن يكون هذا هو اسم جهاز kernel للجهاز، أو اسم للجهاز، مثل rearview. يتم اختيار قيمة هذه السلسلة من خلال تنفيذ HAL ويتم استخدامها بشكل غير شفاف بواسطة الحزمة أعلاه.vendor_flags
. هي طريقة لتمرير معلومات الكاميرا المتخصصة بشكل غير مفهوم من السائق إلى تطبيق EVS المخصّص. يتم تمرير هذه المعلومات بدون تفسيرها من السائق إلى تطبيق EVS، ويمكن تجاهلها مجانًا.
enum DisplayState : uint32 { NOT_OPEN, // Display has not been “opened” yet NOT_VISIBLE, // Display is inhibited VISIBLE_ON_NEXT_FRAME, // Will become visible with next frame VISIBLE, // Display is currently active DEAD, // Display is not available. Interface should be closed }
يصف حالة شاشة EVS، والتي يمكن أن تكون متوقفة (لا تظهر
للسائق) أو مفعَّلة (تعرض صورة للسائق).
يتضمّن حالة انتقالية لا تكون فيها الشاشة مرئية بعد، ولكن يتم إعدادها
ليصبح ظهورها مرئيًا عند إرسال الإطار التالي من الصور مع
طلب returnTargetBufferForDisplay()
.
مدير EVS
يوفر مدير EVS الواجهة العامة لنظام EVS لجمع طرق عرض الكاميرا الخارجية وتقديمها. عندما تسمح برامج تشغيل الأجهزة بواجهة نشطة واحدة فقط لكل مورد (كاميرا أو شاشة)، يسهّل "مدير EVS" الوصول المشترَك إلى الكاميرات. يُعد تطبيق EVS الأساسي أول عميل لجهاز EVS Manager، وهو العميل الوحيد المسموح له بكتابة بيانات العرض (يمكن منح عملاء إضافيين إذن الوصول للقراءة فقط إلى صور الكاميرا).
ينفِّذ "مدير EVS" واجهة برمجة التطبيقات نفسها المستخدَمة في برامج تشغيل HAL الأساسية، ويقدّم خدمة موسّعة من خلال إتاحة استخدام عدّة عملاء متزامنين (يمكن لأكثر من عميل واحد فتح كاميرا من خلال "مدير EVS" وتلقّي بث فيديو).
لا تلاحظ التطبيقات أي اختلافات عند تشغيلها من خلال تنفيذ بروتوكول HAL لأجهزة EVS أو واجهة برمجة تطبيقات EVS Manager، باستثناء أنّ EVS Manager API تسمح بالوصول المتزامن إلى بث الكاميرا. مدير EVS هو العميل الوحيد المسموح له باستخدام طبقة HAL لأجهزة EVS، ويعمل كوكيل لطبقة HAL لأجهزة EVS.
لا تصف الأقسام التالية سوى تلك المكالمات التي لها سلوك مختلف (موسّع) في عملية تنفيذ EVS Manager، وتكون المكالمات المتبقية متطابقة مع أوصاف HAL في EVS.
IEvsEnumerator
openCamera(string camera_id) generates (IEvsCamera camera);
تحصل على عنصر واجهة يُستخدَم للتفاعل مع كاميرا معيّنة
يتم تحديدها من خلال السلسلة الفريدة camera_id. تعرِض قيمة فارغة في حال تعذّر إكمالها.
في طبقة "مدير EVS"، طالما أنّ موارد النظام كافية،
يمكن أن تفتح عملية أخرى كاميرا مفتوحة من قبل، ما يسمح
بتوجيه بث الفيديو إلى تطبيقات مستهلكين متعددة. سلاسل camera_id
في طبقة "مدير EVS" هي نفسها سلاسل camera_id
التي يتم الإبلاغ عنها في طبقة "أجهزة EVS".
IEvsCamera
إنّ تنفيذ IEvsCamera الذي يوفّره "مدير EVS" يتم تحويله إلى آلة افتراضية داخليًا كي لا تؤثّر العمليات التي يجريها أحد العملاء على الكاميرا في العملاء الآخرين، ما يسمح لهم بالوصول بشكل مستقل إلى كاميراتهم.
startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);
بدء عمليات بث الفيديو يمكن للعملاء بدء عمليات بث الفيديو وإيقافها بشكل مستقل على الكاميرا الأساسية نفسها. تبدأ الكاميرا الأساسية عند بدء العميل الأول.
doneWithFrame(uint32 frameId, handle bufferHandle) generates (EvsResult result);
لعرض إطار. يجب على كل عميل إعادة إطاراته عند الانتهاء، ولكن يُسمح له بالاحتفاظ بإطاراته طيلة الوقت الذي يرغب فيه. عندما يصل عدد اللقطات التي يحتفظ بها العميل إلى الحد الأقصى الذي تم ضبطه، لن يتلقّى العميل أي لقطات أخرى إلى أن يعرض لقطة. ولا يؤثّر تخطّي اللقطات هذا في العملاء الآخرين الذين يواصلون تلقّي كل اللقطات على النحو المتوقّع.
stopVideoStream();
إيقاف بث فيديو يمكن لكل عميل إيقاف بث الفيديو في أي وقت بدون التأثير في العملاء الآخرين. يتم إيقاف بث الكاميرا الأساسي في طبقة الأجهزة عندما يوقف العميل الأخير لكاميرا معيّنة بثها.
setExtendedInfo(int32 opaqueIdentifier, int32 opaqueValue) generates (EvsResult result);
تُرسِل قيمة خاصة بالسائق، ما قد يسمح لعميل واحد بالتأثير في العميل الآخر. بما أنّ "مدير EVS" لا يمكنه فهم الآثار المترتبة على كلمات التحكّم التي يحدّدها المورّد، لا يتمّ تحويلها إلى كلمات افتراضية، وتنطبق أيّ آثار جانبية على جميع عملاء كاميرا معيّنة. على سبيل المثال، إذا استخدم أحد المورّدين هذا الطلب لتغيير معدّلات عرض اللقطات، سيتلقّى جميع عملاء كاميرا طبقة الأجهزة المتأثرة اللقطات بمعدّل العرض الجديد.
IEvsDisplay
يُسمح بمالك واحد فقط للشاشة، حتى على مستوى "مدير EVS". لا يضيف الرمز المبرمَج Manager أي وظيفة، بل يمرّر واجهة IEvsDisplay مباشرةً إلى تنفيذ HAL الأساسي.
تطبيق EVS
يتضمّن نظام التشغيل Android تطبيقًا مرجعيًا أصليًا لـ C++ لنظام EVS يتواصل مع "مدير EVS" وHAL للمركبة لتوفير وظائف أساسية لكاميرا الرؤية الخلفية. ومن المتوقع أن يبدأ التطبيق في وقت مبكر جدًا من عملية تشغيل النظام، مع عرض فيديو مناسب بناءً على الكاميرات المتاحة وحالة السيارة (حالة الترس وإشارة الدوران). يمكن لمصنّعي السيارات الأصليين تعديل تطبيق EVS أو استبداله بتطبيق يتضمن منطقًا وعرضًا خاصًا بالمركبة.
ولأنّ بيانات الصورة يتم تقديمها إلى التطبيق في مخزن عادي للرسومات، فإنّ التطبيق مسؤول عن نقل الصورة من المخزن المؤقت المصدر إلى المخزن المؤقت للمخرجات. على الرغم من أنّ هذا الإجراء يؤدي إلى زيادة تكلفة نسخ البيانات، فإنه يمنح التطبيق أيضًا فرصة عرض الصورة في ملف التخزين المؤقت للعرض بأي طريقة يريدها.
على سبيل المثال، قد يختار التطبيق نقل بيانات البكسل بنفسه، وربما باستخدام عملية توسيع أو دوران مضمّنة. يمكن للتطبيق أيضًا اختيار استخدام الصورة المصدر كنسيج OpenGL وعرض مشهد معقّد في ذاكرة التخزين المؤقت للإخراج، بما في ذلك العناصر الافتراضية مثل الرموز والتوجيهات والرسوم المتحركة. قد يختار تطبيق أكثر تعقيدًا أيضًا عدة كاميرات إدخال متزامنة ويدمجها في إطار الإخراج الفردي (مثلاً لاستخدامها في عرض افتراضي من الأعلى للأسفل للمناطق المحيطة بالمركبة).
استخدام EGL/SurfaceFlinger في HAL لشاشة EVS
يوضّح هذا القسم كيفية استخدام EGL لعرض تنفيذ HAL لشاشة EVS في Android 10.
إنّ تنفيذ مرجع EGL يستخدم EGL لعرض معاينة الكاميرا على الشاشة، ويستخدم libgui
لإنشاء سطح عرض EGL المستهدف. في Android 8 (والإصدارات الأحدث)، يتم تصنيف libgui
على أنّه VNDK-private،
ويشير ذلك إلى مجموعة من المكتبات المتاحة لمكتبات VNDK والتي لا يمكن لعمليات المورّدين استخدامها.
وبما أنّ عمليات تنفيذ HAL يجب أن تكون موجودة في قسم المورّد، يتم منع المورّدين من استخدام
Surface في عمليات تنفيذ HAL.
إنشاء libgui لعمليات البائعين
يُعدّ استخدام libgui
هو الخيار الوحيد لاستخدام EGL/SurfaceFlinger
في عمليات تنفيذ HAL لشاشة EVS. إنّ أسهل طريقة لتنفيذ libgui
هي
من خلال
frameworks/native/libs/gui
مباشرةً باستخدام هدف إنشاء إضافي في نص إنشاء التطبيق. هذا الاستهداف مطابق تمامًا لهدف
libgui
باستثناء إضافة حقلَين:
name
vendor_available
cc_library_shared { name: "libgui_vendor", vendor_available: true, vndk: { enabled: false, }, double_loadable: true,
defaults: ["libgui_bufferqueue-defaults"],
srcs: [ … // bufferhub is not used when building libgui for vendors target: { vendor: { cflags: [ "-DNO_BUFFERHUB", "-DNO_INPUT", ], …
ملاحظة: يتم إنشاء استهدافات المورّدين باستخدام وحدة ماكرو NO_INPUT
، ما يؤدي إلى إزالة كلمة واحدة 32 بت من بيانات الطرود. ولأنّ SurfaceFlinger يتوقّع هذا الحقل الذي تمّت إزالته، يتعذّر على SurfaceFlinger تحليل الحزمة. ويظهر ذلك على أنّه خطأ fcntl
:
W Parcel : Attempt to read object from Parcel 0x78d9cffad8 at offset 428 that is not in the object list E Parcel : fcntl(F_DUPFD_CLOEXEC) failed in Parcel::read, i is 0, fds[i] is 0, fd_count is 20, error: Unknown error 2147483647 W Parcel : Attempt to read object from Parcel 0x78d9cffad8 at offset 544 that is not in the object list
لحلّ هذا الشرط:
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 6066421fa..25cf5f0ce 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -54,6 +54,9 @@ status_t layer_state_t::write(Parcel& output) const output.writeFloat(color.b); #ifndef NO_INPUT inputInfo.write(output); +#else + // Write a dummy 32-bit word. + output.writeInt32(0); #endif output.write(transparentRegion); output.writeUint32(transform);
في ما يلي نموذج تعليمات
الإنشاء. من المتوقّع أن يصلك $(ANDROID_PRODUCT_OUT)/system/lib64/libgui_vendor.so
.
$ cd <your_android_source_tree_top> $ . ./build/envsetup. $ lunch <product_name>-<build_variant> ============================================ PLATFORM_VERSION_CODENAME=REL PLATFORM_VERSION=10 TARGET_PRODUCT=<product_name> TARGET_BUILD_VARIANT=<build_variant> TARGET_BUILD_TYPE=release TARGET_ARCH=arm64 TARGET_ARCH_VARIANT=armv8-a TARGET_CPU_VARIANT=generic TARGET_2ND_ARCH=arm TARGET_2ND_ARCH_VARIANT=armv7-a-neon TARGET_2ND_CPU_VARIANT=cortex-a9 HOST_ARCH=x86_64 HOST_2ND_ARCH=x86 HOST_OS=linux HOST_OS_EXTRA=<host_linux_version> HOST_CROSS_OS=windows HOST_CROSS_ARCH=x86 HOST_CROSS_2ND_ARCH=x86_64 HOST_BUILD_TYPE=release BUILD_ID=QT OUT_DIR=out ============================================
$ m -j libgui_vendor … $ find $ANDROID_PRODUCT_OUT/system -name "libgui_vendor*" .../out/target/product/hawk/system/lib64/libgui_vendor.so .../out/target/product/hawk/system/lib/libgui_vendor.so
استخدام أداة الربط في تنفيذ HAL لبروتوكول EVS
في نظام التشغيل Android 8 (والإصدارات الأحدث)، أصبحت عقدة الجهاز /dev/binder
حصرية
لعمليات إطار العمل، وبالتالي يتعذّر على عمليات المورِّد الوصول إليها. بدلاً من ذلك، يجب أن تستخدم
عمليات المورّدين /dev/hwbinder
وأن تحوّل أي واجهات AIDL
إلى HIDL. بالنسبة إلى المستخدمين الذين يريدون مواصلة استخدام واجهات AIDL بين عمليات المورّدين،
استخدِم نطاق الربط /dev/vndbinder
.
نطاق IPC | الوصف |
---|---|
/dev/binder |
تبادل البيانات بين عمليات التطبيق أو إطار العمل باستخدام واجهات AIDL |
/dev/hwbinder |
تبادل البيانات بين عمليات إطار العمل/المورّد باستخدام واجهات HIDL تبادل البيانات بين عمليات المورّد باستخدام واجهات HIDL |
/dev/vndbinder |
واجهة برمجة التطبيقات بين عمليات المورّد/المورّد باستخدام واجهات AIDL |
في حين أنّ SurfaceFlinger تحدِّد واجهات AIDL، يمكن لعمليات المورّدين استخدام واجهات HIDL فقط
للتواصل مع عمليات إطار العمل. يتطلّب تحويل واجهات
AIDL الحالية إلى HIDL قدرًا كبيرًا من العمل. لحسن الحظ، يوفّر Android طريقة يمكن من خلالها اختيار برنامج تشغيل
الحافظة libbinder
والتي ترتبط بها عمليات مكتبة مساحة المستخدم.
diff --git a/evs/sampleDriver/service.cpp b/evs/sampleDriver/service.cpp index d8fb3166..5fd02935 100644 --- a/evs/sampleDriver/service.cpp +++ b/evs/sampleDriver/service.cpp @@ -21,6 +21,7 @@ #include <utils/Errors.h> #include <utils/StrongPointer.h> #include <utils/Log.h> +#include <binder/ProcessState.h> #include "ServiceNames.h" #include "EvsEnumerator.h" @@ -43,6 +44,9 @@ using namespace android; int main() { ALOGI("EVS Hardware Enumerator service is starting"); + // Use /dev/binder for SurfaceFlinger + ProcessState::initWithDriver("/dev/binder"); + // Start a thread to listen to video device addition events. std::atomic<bool> running { true }; std::thread ueventHandler(EvsEnumerator::EvsUeventThread, std::ref(running));
ملاحظة: يجب أن تطلب العمليات لدى المورّدين هذا قبل إرسال طلب إلى Process
أو IPCThreadState
، أو قبل إجراء أي طلبات لجداول البيانات.
سياسات SELinux
في حال وصول مستوى تنفيذ الجهاز إلى ثلاثة أضعاف، يمنع SELinux عمليات المورِّد من استخدام /dev/binder
. على سبيل المثال، يتم منح تنفيذ نموذج HAL لنظام EVS
للنطاق hal_evs_driver
ويتطلب
أذونات القراءة/الكتابة للنطاق binder_device
.
W ProcessState: Opening '/dev/binder' failed: Permission denied F ProcessState: Binder driver could not be opened. Terminating. F libc : Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 9145 (android.hardwar), pid 9145 (android.hardwar) W android.hardwar: type=1400 audit(0.0:974): avc: denied { read write } for name="binder" dev="tmpfs" ino=2208 scontext=u:r:hal_evs_driver:s0 tcontext=u:object_r:binder_device:s0 tclass=chr_file permissive=0
ومع ذلك، تؤدي إضافة هذه الأذونات إلى تعذُّر إنشاء التطبيق لأنّها تنتهك قواعد
neverallow التالية المحدّدة في system/sepolicy/domain.te
لجهاز مزوّد بتقنية الترابيل الكاملة.
libsepol.report_failure: neverallow on line 631 of system/sepolicy/public/domain.te (or line 12436 of policy.conf) violated by allow hal_evs_driver binder_device:chr_file { read write }; libsepol.check_assertions: 1 neverallow failures occurred
full_treble_only(` neverallow { domain -coredomain -appdomain -binder_in_vendor_violators } binder_device:chr_file rw_file_perms; ')
binder_in_vendor_violators
هي سمة يتم تقديمها لرصد الأخطاء وتقديم الإرشادات اللازمة لتطويرها. ويمكن استخدامها أيضًا
لحلّ مشكلة انتهاك Android 10 الموضّحة أعلاه.
diff --git a/evs/sepolicy/evs_driver.te b/evs/sepolicy/evs_driver.te index f1f31e9fc..6ee67d88e 100644 --- a/evs/sepolicy/evs_driver.te +++ b/evs/sepolicy/evs_driver.te @@ -3,6 +3,9 @@ type hal_evs_driver, domain, coredomain; hal_server_domain(hal_evs_driver, hal_evs) hal_client_domain(hal_evs_driver, hal_evs) +# Allow to use /dev/binder +typeattribute hal_evs_driver binder_in_vendor_violators; + # allow init to launch processes in this context type hal_evs_driver_exec, exec_type, file_type, system_file_type; init_daemon_domain(hal_evs_driver)
بناء تطبيق مرجع HAL EVS كعملية بائع
ويمكنك تطبيق التغييرات التالية على "packages/services/Car/evs/Android.mk
" كمرجع. تأكَّد من أنّ جميع التغييرات الموضّحة مناسبة لعملية التنفيذ.
diff --git a/evs/sampleDriver/Android.mk b/evs/sampleDriver/Android.mk index 734feea7d..0d257214d 100644 --- a/evs/sampleDriver/Android.mk +++ b/evs/sampleDriver/Android.mk @@ -16,7 +16,7 @@ LOCAL_SRC_FILES := \ LOCAL_SHARED_LIBRARIES := \ android.hardware.automotive.evs@1.0 \ libui \ - libgui \ + libgui_vendor \ libEGL \ libGLESv2 \ libbase \ @@ -33,6 +33,7 @@ LOCAL_SHARED_LIBRARIES := \ LOCAL_INIT_RC := android.hardware.automotive.evs@1.0-sample.rc LOCAL_MODULE := android.hardware.automotive.evs@1.0-sample +LOCAL_PROPRIETARY_MODULE := true LOCAL_MODULE_TAGS := optional LOCAL_STRIP_MODULE := keep_symbols @@ -40,6 +41,7 @@ LOCAL_STRIP_MODULE := keep_symbols LOCAL_CFLAGS += -DLOG_TAG=\"EvsSampleDriver\" LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code +LOCAL_CFLAGS += -Iframeworks/native/include #NOTE: It can be helpful, while debugging, to disable optimizations #LOCAL_CFLAGS += -O0 -g diff --git a/evs/sampleDriver/service.cpp b/evs/sampleDriver/service.cpp index d8fb31669..5fd029358 100644 --- a/evs/sampleDriver/service.cpp +++ b/evs/sampleDriver/service.cpp @@ -21,6 +21,7 @@ #include <utils/Errors.h> #include <utils/StrongPointer.h> #include <utils/Log.h> +#include <binder/ProcessState.h> #include "ServiceNames.h" #include "EvsEnumerator.h" @@ -43,6 +44,9 @@ using namespace android; int main() { ALOGI("EVS Hardware Enumerator service is starting"); + // Use /dev/binder for SurfaceFlinger + ProcessState::initWithDriver("/dev/binder"); + // Start a thread to listen video device addition events. std::atomic<bool> running { true }; std::thread ueventHandler(EvsEnumerator::EvsUeventThread, std::ref(running)); diff --git a/evs/sepolicy/evs_driver.te b/evs/sepolicy/evs_driver.te index f1f31e9fc..632fc7337 100644 --- a/evs/sepolicy/evs_driver.te +++ b/evs/sepolicy/evs_driver.te @@ -3,6 +3,9 @@ type hal_evs_driver, domain, coredomain; hal_server_domain(hal_evs_driver, hal_evs) hal_client_domain(hal_evs_driver, hal_evs) +# allow to use /dev/binder +typeattribute hal_evs_driver binder_in_vendor_violators; + # allow init to launch processes in this context type hal_evs_driver_exec, exec_type, file_type, system_file_type; init_daemon_domain(hal_evs_driver) @@ -22,3 +25,7 @@ allow hal_evs_driver ion_device:chr_file r_file_perms; # Allow the driver to access kobject uevents allow hal_evs_driver self:netlink_kobject_uevent_socket create_socket_perms_no_ioctl; + +# Allow the driver to use the binder device +allow hal_evs_driver binder_device:chr_file rw_file_perms;