يحتوي Android على طبقة تجريد أجهزة HIDL (HAL) الخاصة بالسيارات والتي توفر إمكانية التقاط الصور وعرضها في وقت مبكر جدًا من عملية تمهيد Android وتستمر في العمل طوال عمر النظام. يتضمن HAL مجموعة نظام الرؤية الخارجية (EVS) ويستخدم عادةً لدعم كاميرا الرؤية الخلفية وشاشات الرؤية المحيطية في المركبات المزودة بأنظمة المعلومات والترفيه داخل السيارة (IVI) المستندة إلى Android. تتيح EVS أيضًا تنفيذ الميزات المتقدمة في تطبيقات المستخدم.
يتضمن Android أيضًا واجهة برنامج تشغيل العرض والالتقاط الخاصة بـ EVS (في /hardware/interfaces/automotive/evs/1.0
). في حين أنه من الممكن إنشاء تطبيق كاميرا للرؤية الخلفية فوق كاميرا Android الحالية وخدمات العرض، فمن المحتمل أن يتم تشغيل مثل هذا التطبيق بعد فوات الأوان في عملية تشغيل Android. يؤدي استخدام HAL المخصص إلى تمكين واجهة مبسطة ويوضح ما يحتاج صانع المعدات الأصلية إلى تنفيذه لدعم مكدس EVS.
مكونات النظام
يتضمن EVS مكونات النظام التالية:
![مخطط مكونات نظام EVS](https://source.android.com/static/docs/automotive/images/vhal_evs_components.png?authuser=6&hl=ar)
تطبيق 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.](https://source.android.com/static/docs/automotive/images/vhal_evs_manager.png?authuser=6&hl=ar)
لا ترى التطبيقات أي اختلافات عند التشغيل من خلال تطبيق 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 أو استبداله بالمنطق والعرض التقديمي الخاصين بالمركبة.
![](https://source.android.com/static/docs/automotive/images/vhal_evs_get_camera.png?authuser=6&hl=ar)
![](https://source.android.com/static/docs/automotive/images/vhal_evs_receive_frame.png?authuser=6&hl=ar)
نظرًا لأنه يتم تقديم بيانات الصورة إلى التطبيق في مخزن مؤقت للرسومات قياسي، يكون التطبيق مسؤولاً عن نقل الصورة من المخزن المؤقت المصدر إلى المخزن المؤقت للإخراج. على الرغم من أن هذا يؤدي إلى تكلفة نسخة البيانات، فإنه يوفر أيضًا الفرصة للتطبيق لعرض الصورة في المخزن المؤقت للعرض بأي طريقة يريدها.
على سبيل المثال، قد يختار التطبيق نقل بيانات البكسل نفسها، ربما باستخدام مقياس مضمّن أو عملية تدوير. يمكن أن يختار التطبيق أيضًا استخدام الصورة المصدر كنسيج 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;