HAL למצלמת רכב

Android מכיל שכבת הפשטה לחומרה (HAL) של HIDL לכלי רכב, שמאפשרת לצלם תמונות ולהציג אותן בשלב מוקדם מאוד בתהליך האתחול של Android, וממשיכה לפעול כל עוד המערכת פועלת. תקן HAL כולל את סטאק המערכת החיצונית (EVS), ובדרך כלל משמש לתמיכה בצגי מצלמה אחורית וסראונד בכלי רכב עם מערכות מידע מובנה ברכב (IVI). EVS מאפשר גם להטמיע תכונות מתקדמות באפליקציות של משתמשים.

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

רכיבי המערכת

EVS כולל את רכיבי המערכת הבאים:

תרשים של רכיבי המערכת של EVS
איור 1. סקירה כללית של רכיבי המערכת של EVS.

אפליקציית EVS

אפליקציה לדוגמה של C++ EVS (/packages/services/Car/evs/app) משמשת כהטמעה לקובצי עזר. האפליקציה הזו אחראית לבקש מסגרות וידאו ממנהל ה-EVS ולשלוח מסגרות מוגמרות להצגה בחזרה למנהל ה-EVS. הוא אמור להתחיל על ידי init ברגע ש-EVS ו-Car Service יהיו זמינים, תוך שתיים (2) שניות ממועד ההפעלה. יצרני ציוד מקורי יכולים לשנות או להחליף את אפליקציית EVS לפי הצורך.

מנהל כלי רכב חשמליים (EVS)

הכלי לניהול הרכב החשמלי (EVS Manager) (/packages/services/Car/evs/manager) מספק את אבני הבניין שנדרשות לאפליקציה של הרכב החשמלי (EVS) כדי להטמיע תוכן מתצוגה פשוטה של המצלמה האחורית ועד לעיבוד של כמה מצלמות ב-6DOF. הממשק שלו מוצג באמצעות HIDL והוא מתוכנן לקבל כמה לקוחות בו-זמנית. אפליקציות ושירותים אחרים (במיוחד שירות הרכב) יכולים לשלוח שאילתה על מצב מנהל הרכבים החשמליים כדי לברר מתי מערכת הרכב החשמלי (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 לשימוש חוזר. יש מספר קטן ומוגבל של מאגרי נתונים זמינים (יכול להיות שרק אחד), ואם המלאי נגמר, לא יישלחו עוד פריימים עד שיוחזר מאגר נתונים, וכתוצאה מכך יכול להיות שפריימים יימחקו (מאגר נתונים עם אחיזה 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. התוסף הזה זמין רק לצורך שימוש בתוספים ספציפיים לרכב, ולא נדרש שהקריאה הזו תפעל במצב ברירת מחדל. אם הנהג מזהה את הערכים ומקבל אותם, צריך להחזיר את הערך 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 בכל פעם שפריים של סרטון מוכן לבדיקה. כדי להחזיר כינויים של מאגרי נתונים זמניים שמתקבלים בשיטה הזו, צריך לבצע קריאות ל-IEvsCamera::doneWithFrame(). כשזרם הווידאו מופסק באמצעות קריאה ל-IEvsCamera::stopVideoStream(), הקריאה החוזרת (callback) עשויה להמשיך בזמן שצינור עיבוד הנתונים מתרוקן. עדיין צריך להחזיר כל פריים. כשהפריים האחרון בסטרימינג נמסר, נשלחת הודעה bufferHandle של NULL, שמסמנת את סוף הסטרימינג ולא מתבצעות עוד העברות של פריימים. אין צורך לשלוח בחזרה את הערך NULL‏ bufferHandle עם 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, וניתן להשתמש בהם (ולהשתמש בהם שוב) לפי הצורך.

רשת המדיה 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. הפונקציה תמיד מחזירה את הערך OK, אלא אם המצב המבוקש הוא ערך enum לא מזוהה. במקרה כזה, הפונקציה מחזירה את הערך INVALID_ARG.

getDisplayState() generates (DisplayState state);

מקבל את מצב התצוגה. הטמעת HAL צריכה לדווח על המצב הנוכחי בפועל, שעשוי להיות שונה מהמצב המבוקש לאחרונה. הלוגיקה שאחראית לשינוי מצבי התצוגה צריכה להיות מעל שכבת המכשיר, ולכן לא רצוי שההטמעה של HAL תשנה באופן ספונטני את מצבי התצוגה.

getTargetBuffer() generates (handle bufferHandle);

הפונקציה מחזירה אחיזה למאגר פריימים שמשויך למסך. התוכנה ו/או ה-GL יכולים לנעול את המאגר הזה ולכתוב בו. צריך להחזיר את המאגר הזה באמצעות קריאה ל-returnTargetBufferForDisplay() גם אם המסך כבר לא גלוי.

למרות שיש אפשרות טכנית לפורמטים של מאגר נתונים זמני קנייני, כדי לבדוק תאימות צריך שהמאגר הזמני יהיה באחד מארבעת הפורמטים הנתמכים: NV21 (YCrCb YCrCb 4:2:0 Semi-Planar), YV12 (YCrCb 4:2:0 Planar), YUYV (YCrCb 4:2:2 bit: RGBA), RGBA . הפורמט שנבחר חייב להיות יעד עיבוד (render target) תקין של 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). ה-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 ולקבל סטרימינג של וידאו).

תרשים של EVS Manager ו-EVS Hardware API.
איור 2. EVS Manager משקף את ה-API של החומרה של EVS
.

לאפליקציות אין הבדל בין הפעלה באמצעות הטמעת 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.

מצלמת IEvs

ההטמעה של 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 לא יכול להבין את ההשלכות של מילות בקרה שהוגדרו על ידי הספק, הן לא עוברות וירטואליזציה וכל ההשפעות הצדדיות חלות על כל הלקוחות של מצלמה נתונה. לדוגמה, אם ספק השתמש בקריאה הזו כדי לשנות את שיעורי הפריימים, כל הלקוחות של המצלמה בשכבת החומרה המושפעת יקבלו פריימים בקצב החדש.

רשת המדיה IEvs

מותר להגדיר רק בעלים אחד של המסך, גם ברמת מנהל ה-EVS. ה-Manager לא מוסיף פונקציונליות, אלא רק מעביר את הממשק IEvsDisplay ישירות להטמעת ה-HAL הבסיסית.

אפליקציית EVS

Android כולל הטמעת עזרה מקורית ב-C++ של אפליקציית EVS שמתקשרת עם מנהל ה-EVS ועם ה-HAL של הרכב כדי לספק פונקציות בסיסיות של מצלמת הרכב. האפליקציה אמורה להתחיל לפעול בשלב מוקדם מאוד בתהליך האתחול של המערכת, עם וידאו מתאים בהתאם למצלמות הזמינות ולמצב הרכב (מצב ההילוכים והאיתות). יצרני ציוד מקורי יכולים לשנות או להחליף את אפליקציית ה-EVS בלוגיקה ובתצוגה ספציפיות לרכב.

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


איור 4. לוגיקה לדוגמה של אפליקציית EVS, קבלת קריאה חוזרת (callback) של מסגרת.

מאחר שנתוני התמונה מוצגים לאפליקציה במאגר נתונים זמני גרפי רגיל, האפליקציה אחראית להעברת התמונה ממאגר הנתונים הזמני של המקור למאגר הנתונים הזמני של הפלט. אמנם הדבר כרוך בעלות של העתקת נתונים, אבל הוא גם מאפשר לאפליקציה ליצור את התמונה במאגר התצוגה בכל דרך שהיא רוצה.

לדוגמה, יכול להיות שהאפליקציה תבחור להעביר את נתוני הפיקסלים עצמם, אולי עם פעולת סיבוב או שינוי קנה מידה בתוך שורת הקוד. האפליקציה יכולה גם להשתמש בתמונה המקורית כטקסטורה של OpenGL ולייצר עיבוד (רנדור) של סצנה מורכבת למאגר הפלט, כולל אלמנטים וירטואליים כמו סמלים, הנחיות ואנימציות. אפליקציה מתוחכמת יותר יכולה גם לבחור כמה מצלמות קלט בו-זמנית ולמזג אותן למסגרת פלט אחת (למשל, לשימוש בתצוגה וירטואלית מלמעלה-למטה של סביבת הרכב).

שימוש ב-EGL/SurfaceFlinger ב-HAL של תצוגת EVS

בקטע הזה נסביר איך להשתמש ב-EGL כדי ליצור עיבוד (רנדור) של הטמעת EVS Display HAL ב-Android 10.

הטמעת HAL של EVS משתמשת ב-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 מצפה שהשדה הזה הוסר, 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

אם הטמעת המכשיר מתבצעת בטרבל מלא, SELinux מונע מהתהליכים של הספק להשתמש ב-/dev/binder. לדוגמה, הטמעה של דוגמת HAL של EVS מוקצית לדומיין 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

עם זאת, הוספת ההרשאות האלה גורמת לכשל ב-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)

פיתוח הטמעת העזר של 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;