Android מכיל שכבת הפשטה של חומרה (HAL) לכלי רכב מסוג HIDL, שמאפשרת לצלם תמונות ולהציג אותן בשלב מוקדם מאוד בתהליך האתחול של Android, וממשיכה לפעול כל עוד המערכת פועלת. ה-HAL כולל את מחסנית מערכת התצוגה החיצונית (EVS), ובדרך כלל משמש לתמיכה במצלמת הרכב האחורית ובתצוגות של תצוגת היקף הרכב ברכב עם מערכות מידע ובידור ברכב (IVI) מבוססות-Android. EVS מאפשר גם להטמיע תכונות מתקדמות באפליקציות של משתמשים.
Android כולל גם ממשק של מנהל התקן לצילום ולתצוגה שספציפי ל-EVS (ב-/hardware/interfaces/automotive/evs/1.0
). אפשר ליצור אפליקציה למצלמת נסיעה לאחור נוסף על שירותי המצלמה והמסך הקיימים של Android, אבל סביר להניח שהאפליקציה הזו תפעל מאוחר מדי בתהליך האתחול של Android. השימוש ב-HAL ייעודי מאפשר ממשק פשוט ומבהיר מה יצרני ציוד מקורי צריכים להטמיע כדי לתמוך ב-EVS stack.
רכיבי המערכת
EVS כולל את רכיבי המערכת הבאים:

איור 1. סקירה כללית של רכיבי המערכת של EVS.
אפליקציה לרכבים חשמליים
אפליקציית EVS לדוגמה ב-C++ (/packages/services/Car/evs/app
) משמשת כהטמעה לדוגמה. האפליקציה הזו אחראית לבקש מסגרות וידאו ממנהל ה-EVS ולשלוח מסגרות מוגמרות להצגה בחזרה למנהל ה-EVS.
הוא אמור להתחיל על ידי init ברגע ש-EVS ו-Car Service יהיו זמינים, תוך שתיים (2) שניות ממועד ההפעלה. יצרני ציוד מקורי יכולים לשנות או להחליף את אפליקציית EVS לפי הצורך.
EVS Manager
מנהל ה-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
.
יצרן הציוד המקורי אחראי להטמעת ה-API שמתואר בקובצי ה-hal . ב-/hardware/interfaces/automotive/evs
. הטמעות כאלה אחראיות על הגדרה ואיסוף של נתונים ממצלמות פיזיות, ועל העברת הנתונים דרך מאגרי זיכרון משותפים ש-Gralloc יכול לזהות. הצד של המסך בהטמעה אחראי לספק מאגר זיכרון משותף שאפשר למלא באמצעות האפליקציה (בדרך כלל באמצעות עיבוד גרפי של EGL) ולהציג את המסגרות שהושלמו במקום כל דבר אחר שעשוי להופיע במסך הפיזי. הטמעות של ממשק EVS על ידי ספקים עשויות להישמר ב-/vendor/… /device/…
או ב-hardware/…
(למשל,
/hardware/[vendor]/[platform]/evs
).
מנהלי התקן של ליבה
במכשיר שתומך ב-EVS stack נדרשים מנהלי ליבה. במקום ליצור מנהלי התקנים חדשים, יצרני ציוד מקורי יכולים לתמוך בתכונות הנדרשות ל-EVS באמצעות מנהלי התקנים קיימים של חומרת מצלמה ו/או תצוגה. שימוש חוזר ב-drivers יכול להיות יתרון, במיוחד ב-display drivers שבהם הצגת התמונה עשויה לדרוש תיאום עם שרשראות אחרות פעילות. Android 8.0 כולל מנהל התקן לדוגמה שמבוסס על v4l2 (ב-packages/services/Car/evs/sampleDriver
) שמסתמך על הליבה לתמיכה ב-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
: מחרוזת שמזהה באופן ייחודי מצלמה מסוימת. יכול להיות שם המכשיר בליבה או שם למכשיר, כמו 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
, ולאחר ההתחלה הם חייבים להיווצר בקצב של לפחות 10FPS. הזמן הנדרש להפעלת סטרימינג של וידאו נספר כחלק מכל דרישה לזמן הפעלה של מצלמת הרכב. אם הסטרימינג לא התחיל, צריך להחזיר קוד שגיאה. אחרת, צריך להחזיר את הערך OK.
oneway doneWithFrame(BufferDesc buffer);
הפונקציה מחזירה פריים שנשלח על ידי IEvsCameraStream. כשמסיימים להשתמש בפריים שנשלח לממשק IEvsCameraStream, צריך להחזיר את הפריים ל-IEvsCamera לשימוש חוזר. יש מספר קטן ומוגבל של מאגרי נתונים זמינים (יכול להיות שרק אחד), ואם המלאי נגמר, לא יישלחו עוד פריימים עד שיוחזר מאגר נתונים, וכתוצאה מכך יכול להיות שפריימים יימחקו (מאגר נתונים עם אחיזה (handle) null מציין את סוף הסטרימינג ואין צורך להחזיר אותו באמצעות הפונקציה הזו). הפונקציה מחזירה את הערך 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 }
תיאור של תמונה שהועברה דרך ה-API. ה-HAL drive אחראי למלא את המבנה הזה כדי לתאר את מאגר התמונות, ולקוח ה-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 כדי לאפשר זיהוי של מאגר אחרי נסיעה הלוך ושוב דרך ממשקי ה-API של HAL. הערך ששמור בשדה הזה עשוי להיבחר באופן שרירותי על ידי הטמעת ה-HAL.memHandle
. ה-handle של מאגר הזיכרון הבסיסי שמכיל את נתוני התמונה. יכול להיות שהטמעת ה-HAL תשמור כאן את ה-handle של מאגר Gralloc.
IEvsCameraStream
הלקוח מטמיע את הממשק הזה כדי לקבל העברות אסינכררוניות של פריימים של וידאו.
deliverFrame(BufferDesc buffer);
מקבלת קריאות מ-HAL בכל פעם שפריים של סרטון מוכן לבדיקה.
יש להחזיר את ה-handles של המאגרים שהתקבלו באמצעות ה-method הזה באמצעות קריאות ל-IEvsCamera::doneWithFrame()
. כששידור הווידאו מושהה באמצעות קריאה ל-IEvsCamera::stopVideoStream()
, יכול להיות שהקריאה החוזרת (callback) הזו תמשיך עד שצינור עיבוד הנתונים ינוקז. עדיין צריך להחזיר כל פריים. אחרי שהפריים האחרון בסטרימינג יועבר, יועבר bufferHandle של NULL, שמציין את סוף הסטרימינג ולא יתבצעו העברות נוספות של פריימים. אין צורך לשלוח בחזרה את ה-bufferHandle של NULL דרך doneWithFrame()
, אבל צריך להחזיר את כל ה-handles האחרים
מבחינה טכנית, אפשר להשתמש בפורמטים קנייניים של מאגרים, אבל כדי לבדוק את התאימות, המאגר צריך להיות באחד מארבעת הפורמטים הנתמכים: 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, אלא אם המצב המבוקש הוא ערך enum לא מזוהה. במקרה כזה, הפונקציה מחזירה את הערך 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 של הפלטפורמה.
במקרה של שגיאה, המערכת מחזירה מאגר עם אחיזה (handle) של null, אבל אין צורך להעביר מאגר כזה בחזרה אל returnTargetBufferForDisplay
.
returnTargetBufferForDisplay(handle bufferHandle) generates (EvsResult result);
הודעה לצג על כך שהמאגר מוכן להצגה. רק מאגרי נתונים שאוחזרו באמצעות קריאה ל-getTargetBuffer()
תקפים לשימוש בקריאה הזו, ואפליקציית הלקוח לא יכולה לשנות את התוכן של BufferDesc
. אחרי הקריאה הזו, מאגר הנתונים לא תקף יותר לשימוש על ידי הלקוח. הפונקציה מחזירה את הערך OK אם הפעולה בוצעה בהצלחה, או קוד שגיאה מתאים, שעשוי לכלול את הערכים INVALID_ARG
או BUFFER_NOT_AVAILABLE
.
struct DisplayDesc { string display_id; int32 vendor_flags; // Opaque value }
תיאור המאפיינים הבסיסיים של תצוגת EVS הנדרשים להטמעת EVS. ה-HAL אחראי למלא את המבנה הזה כדי לתאר את תצוגת ה-EVS. יכול להיות מסך פיזי או מסך וירטואלי שמופיע כשכבת-על או מעורבב עם מכשיר אחר להצגת מצגות.
display_id
. מחרוזת שמזהה באופן ייחודי את המסך. זה יכול להיות שם המכשיר בליבה, או שם למכשיר, כמו rearview. הערך של המחרוזת הזו נבחר על ידי הטמעת HAL, והמידע הזה משומש באופן מעורפל על ידי הסטאק שלמעלה.vendor_flags
. שיטה להעברת מידע מיוחד של מצלמה באופן מעורפל מהדרייבר לאפליקציית EVS בהתאמה אישית. המידע מועבר מהדרייבר לאפליקציית ה-EVS ללא פרשנות, והיא יכולה להתעלם ממנו.
enum DisplayState : uint32 { NOT_OPEN, // Display has not been “opened” yet NOT_VISIBLE, // Display is inhibited VISIBLE_ON_NEXT_FRAME, // Will become visible with next frame VISIBLE, // Display is currently active DEAD, // Display is not available. Interface should be closed }
תיאור המצב של תצוגת ה-EVS. התצוגה יכולה להיות מושבתת (לא מוצגת לנהג) או מופעלת (מוצגת לנהג תמונה).
כולל מצב זמני שבו המסך עדיין לא גלוי, אבל הוא מוכן להפוך לגלוי עם העברת הפריים הבא של התמונות באמצעות הקריאה returnTargetBufferForDisplay()
.
EVS Manager
מנהל ה-EVS מספק את הממשק הציבורי למערכת ה-EVS לאיסוף ולצגת תצוגות של מצלמות חיצוניות. במקרים שבהם מנהלי החומרה מאפשרים רק ממשק פעיל אחד לכל משאב (מצלמה או מסך), מנהל ה-EVS מאפשר גישה משותפת למצלמות. אפליקציית EVS ראשית אחת היא הלקוח הראשון של מנהל ה-EVS, והיא הלקוח היחיד שמותר לו לכתוב נתוני תצוגה (אפשר להעניק ללקוחות נוספים הרשאת קריאה בלבד לתמונות מהמצלמה).
מנהל ה-EVS מטמיע את אותו ממשק API כמו מנהלי ה-HAL הבסיסיים, ומספק שירות מורחב על ידי תמיכה במספר לקוחות בו-זמנית (יותר מלקוח אחד יכול לפתוח מצלמה דרך מנהל ה-EVS ולקבל סטרימינג של וידאו).

איור 2. EVS Manager משקף את EVS Hardware API שמבוסס עליו.
לאפליקציות אין הבדל בין הפעלה באמצעות הטמעת HAL של חומרת EVS לבין הפעלה באמצעות EVS Manager API, מלבד העובדה ש-EVS Manager API מאפשר גישה בו-זמנית לשידור של המצלמה. מנהל ה-EVS הוא הלקוח היחיד שמותר לו להתחבר לשכבת ה-HAL של חומרת ה-EVS, והוא פועל בתור שרת proxy עבור ה-HAL של חומרת ה-EVS.
בקטעים הבאים מתוארות רק הקריאות שיש להן התנהגות שונה (מורחבת) בהטמעה של EVS Manager. הקריאות האחרות זהות לתיאורים של EVS HAL.
IEvsEnumerator
openCamera(string camera_id) generates (IEvsCamera camera);
הפונקציה מקבלת אובייקט ממשק המשמש לאינטראקציה עם מצלמה ספציפית, שמזוהה לפי המחרוזת הייחודית camera_id. הפונקציה מחזירה ערך NULL במקרה של כשל.
בשכבת מנהל ה-EVS, כל עוד יש מספיק משאבי מערכת, תהליך אחר יכול לפתוח מחדש מצלמה שכבר פתוחה, וכך לאפשר יצירת ענפים של סטרימינג של וידאו לכמה אפליקציות של צרכנים. מחרוזות 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);
שליחת ערך ספציפי לנהג, שעלול לאפשר ללקוח אחד להשפיע על לקוח אחר. מאחר שמנהל ה-EVS לא יכול להבין את ההשלכות של מילות בקרה שהוגדרו על ידי הספק, הן לא עוברות וירטואליזציה וכל ההשפעות הצדדיות חלות על כל הלקוחות של מצלמה נתונה. לדוגמה, אם ספק השתמש בקריאה הזו כדי לשנות את שיעורי הפריימים, כל הלקוחות של המצלמה בשכבת החומרה המושפעת יקבלו פריימים בקצב החדש.
IEvsDisplay
מותר להגדיר רק בעלים אחד של המסך, גם ברמת מנהל ה-EVS. ה-Manager לא מוסיף פונקציונליות, אלא רק מעביר את הממשק IEvsDisplay ישירות להטמעת ה-HAL הבסיסית.
אפליקציית EVS
Android כולל הטמעת עזרה מקורית ב-C++ של אפליקציית EVS שמתקשרת עם מנהל ה-EVS ועם ה-HAL של הרכב כדי לספק פונקציות בסיסיות של מצלמת הרכב. האפליקציה אמורה להתחיל לפעול בשלב מוקדם מאוד בתהליך האתחול של המערכת, ותוצג בה וידאו מתאים בהתאם למצלמות הזמינות ולמצב הרכב (מצב ההילוכים והאיתות). יצרני ציוד מקורי יכולים לשנות או להחליף את אפליקציית ה-EVS בלוגיקה ובתצוגה ספציפיות לרכב שלהם.

איור 3. לוגיקה לדוגמה של אפליקציית EVS, אחזור רשימת המצלמות.

איור 4. לוגיקה לדוגמה של אפליקציית EVS, קבלת קריאה חוזרת למסגרת.
מכיוון שנתוני התמונה מוצגים לאפליקציה במאגר נתונים זמני גרפי רגיל, האפליקציה אחראית להעברת התמונה ממאגר הנתונים הזמני של המקור למאגר הנתונים הזמני של הפלט. אמנם הדבר כרוך בעלות של העתקת נתונים, אבל הוא גם מאפשר לאפליקציה ליצור את התמונה במאגר התצוגה בכל דרך שהיא רוצה.
לדוגמה, יכול להיות שהאפליקציה תבחור להעביר את נתוני הפיקסלים עצמם, אולי עם פעולת סיבוב או שינוי קנה מידה בתוך השורה. האפליקציה יכולה גם להשתמש בתמונה המקורית כטקסטורה של OpenGL ולייצר עיבוד (רנדור) של סצנה מורכבת למאגר הפלט, כולל אלמנטים וירטואליים כמו סמלים, הנחיות ואנימציות. אפליקציה מתוחכמת יותר עשויה גם לבחור כמה מצלמות קלט בו-זמנית ולמזג אותן לתוך מסגרת פלט אחת (למשל, לשימוש בתצוגה וירטואלית מלמעלה למטה של הסביבה שמסביב לרכב).
שימוש ב-EGL/SurfaceFlinger ב-HAL של תצוגת EVS
בקטע הזה נסביר איך להשתמש ב-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 ישירות, באמצעות יעד build נוסף בסקריפט ה-build. היעד הזה זהה ליעד 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 מצפה לשדה הזה, שהוסרה, ולכן הוא לא מצליח לנתח את החבילה. המצב הזה יתועד ככישלון 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);
בהמשך מפורטות הוראות build לדוגמה. צפוי להגיע $(ANDROID_PRODUCT_OUT)/system/lib64/libgui_vendor.so
.
$ cd <your_android_source_tree_top> $ . ./build/envsetup. $ lunch <product_name>-<build_variant> ============================================ PLATFORM_VERSION_CODENAME=REL PLATFORM_VERSION=10 TARGET_PRODUCT=<product_name> TARGET_BUILD_VARIANT=<build_variant> TARGET_BUILD_TYPE=release TARGET_ARCH=arm64 TARGET_ARCH_VARIANT=armv8-a TARGET_CPU_VARIANT=generic TARGET_2ND_ARCH=arm TARGET_2ND_ARCH_VARIANT=armv7-a-neon TARGET_2ND_CPU_VARIANT=cortex-a9 HOST_ARCH=x86_64 HOST_2ND_ARCH=x86 HOST_OS=linux HOST_OS_EXTRA=<host_linux_version> HOST_CROSS_OS=windows HOST_CROSS_ARCH=x86 HOST_CROSS_2ND_ARCH=x86_64 HOST_BUILD_TYPE=release BUILD_ID=QT OUT_DIR=out ============================================
$ m -j libgui_vendor … $ find $ANDROID_PRODUCT_OUT/system -name "libgui_vendor*" .../out/target/product/hawk/system/lib64/libgui_vendor.so .../out/target/product/hawk/system/lib/libgui_vendor.so
שימוש ב-binder בהטמעה של EVS HAL
ב-Android 8 ואילך, צומת המכשיר /dev/binder
הפך לבלעדי לתהליכי המסגרת, ולכן לא ניתן לגשת אליו בתהליכים של ספקים. במקום זאת, תהליכי הספקים צריכים להשתמש ב-/dev/hwbinder
ולהמיר ממשקי AIDL ל-HIDL. אם אתם רוצים להמשיך להשתמש בממשקי AIDL בין תהליכי ספקים, תוכלו להשתמש בדומיין ה-binder, /dev/vndbinder
.
דומיין IPC | תיאור |
---|---|
/dev/binder |
IPC בין תהליכים של מסגרת/אפליקציה באמצעות ממשקי AIDL |
/dev/hwbinder |
IPC בין תהליכים של מסגרת/ספק עם ממשקי HIDL IPC בין תהליכים של ספקים עם ממשקי HIDL |
/dev/vndbinder |
IPC בין ספקים/תהליכי ספקים באמצעות ממשקי AIDL |
SurfaceFlinger מגדיר ממשקי AIDL, אבל תהליכי הספקים יכולים להשתמש רק בממשקי HIDL כדי לתקשר עם תהליכי המסגרת. כדי להמיר ממשקי AIDL קיימים ל-HIDL, נדרשת כמות עבודה לא מבוטלת. למרבה המזל, ב-Android יש שיטה לבחירת מנהל ה-binder של libbinder
, שאליו מקושרים תהליכי הספרייה במרחב המשתמש.
diff --git a/evs/sampleDriver/service.cpp b/evs/sampleDriver/service.cpp index d8fb3166..5fd02935 100644 --- a/evs/sampleDriver/service.cpp +++ b/evs/sampleDriver/service.cpp @@ -21,6 +21,7 @@ #include <utils/Errors.h> #include <utils/StrongPointer.h> #include <utils/Log.h> +#include <binder/ProcessState.h> #include "ServiceNames.h" #include "EvsEnumerator.h" @@ -43,6 +44,9 @@ using namespace android; int main() { ALOGI("EVS Hardware Enumerator service is starting"); + // Use /dev/binder for SurfaceFlinger + ProcessState::initWithDriver("/dev/binder"); + // Start a thread to listen to video device addition events. std::atomic<bool> running { true }; std::thread ueventHandler(EvsEnumerator::EvsUeventThread, std::ref(running));
הערה: תהליכים של ספקים צריכים לבצע את הקריאה הזו לפני הקריאה ל-Process
או ל-IPCThreadState
, או לפני ביצוע קריאות ל-binder.
כללי מדיניות SELinux
אם ההטמעה במכשיר היא של תכונות treble מלאות, 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
עם זאת, הוספת ההרשאות האלה גורמת לכשל ב-build כי היא מפירה את כללי 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;