HAL למצלמת רכב

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

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

רכיבי המערכת

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

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

אפליקציית EVS

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

EVS Manager

הכלי EVS Manager‏ (/packages/services/Car/evs/manager) מספק את אבני הבניין שאפליקציית EVS צריכה כדי להטמיע כל דבר, החל מתצוגה פשוטה של מצלמה אחורית ועד לרינדור של כמה מצלמות עם 6 דרגות חופש. הממשק שלה מוצג באמצעות HIDL והוא בנוי לקבל כמה לקוחות בו-זמנית. אפליקציות ושירותים אחרים (במיוחד Car Service) יכולים לשלוח שאילתה ל-EVS Manager כדי לגלות מתי מערכת ה-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 דורש מנהלי התקנים של ליבת המערכת. במקום ליצור מנהלי התקנים חדשים, יצרני ציוד מקורי יכולים לתמוך בתכונות שנדרשות ל-EVS באמצעות מנהלי התקנים קיימים של מצלמה או חומרה של מסך. שימוש חוזר בדרייברים יכול להיות יתרון, במיוחד בדרייברים של תצוגה שבהם הצגת התמונה עשויה לדרוש תיאום עם threads פעילים אחרים. ‫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, ואחרי ההתחלה, הם צריכים להיווצר בקצב של 10 FPS לפחות. הזמן שנדרש כדי להתחיל את שידור הווידאו נספר למעשה כחלק מהזמן שנדרש להפעלת המצלמה האחורית. אם השידור לא מופעל, צריך להחזיר קוד שגיאה. אחרת, מוחזר 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. התוסף הזה מסופק רק כדי להקל על שימוש בתוספים ספציפיים לרכב, ואף הטמעה של 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. נקודת האחיזה של מאגר הזיכרון הבסיסי שמכיל את נתוני התמונה. הטמעת ה-HAL יכולה לבחור לאחסן כאן את הטיפול ב-Gralloc buffer.

IEvsCameraStream

הלקוח מטמיע את הממשק הזה כדי לקבל מסגרות וידאו אסינכרוניות.

deliverFrame(BufferDesc buffer);

מקבלת קריאות מה-HAL בכל פעם שפריים של סרטון מוכן לבדיקה. צריך להחזיר את ה-buffer שמתקבל באמצעות ה-method הזה דרך קריאות ל-IEvsCamera::doneWithFrame(). כשזרם הווידאו מופסק באמצעות קריאה ל-IEvsCamera::stopVideoStream(), יכול להיות שהקריאה החוזרת הזו תמשיך בזמן שהצינור מתרוקן. עדיין צריך להחזיר כל פריים. כשמסיימים להעביר את הפריים האחרון בזרם, מועבר NULL bufferHandle, שמציין את סוף הזרם ולא מתבצעות יותר העברות של פריים. לא צריך להחזיר את הערך NULL bufferHandle עצמו עם doneWithFrame(), אבל צריך להחזיר את כל שאר נקודות האחיזה

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

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

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

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

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

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. ה-Manager לא מוסיף פונקציונליות, והוא פשוט מעביר את הממשק IEvsDisplay ישירות להטמעה הבסיסית של HAL.

אפליקציית EVS

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

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


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

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

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

בהמשך מופיעות הוראות לדוגמה לבנייה. צפוי שתקבלו $(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 הפך לבלעדי לתהליכי framework, ולכן לא ניתן לגשת אליו לתהליכי ספק. במקום זאת, תהליכי הספק צריכים להשתמש ב-/dev/hwbinder ולהמיר כל ממשק AIDL ל-HIDL. אם אתם רוצים להמשיך להשתמש בממשקי AIDL בין תהליכי ספקים, אתם יכולים להשתמש בדומיין של Binder, ‏ /dev/vndbinder.

דומיין IPC תיאור
/dev/binder תקשורת בין תהליכים (IPC) בין תהליכי framework/אפליקציה עם ממשקי AIDL
/dev/hwbinder תקשורת בין תהליכים (IPC) בין תהליכי framework/ספק עם ממשקי HIDL
תקשורת בין תהליכים (IPC) בין תהליכי ספק עם ממשקי HIDL
/dev/vndbinder תקשורת בין תהליכים (IPC) בין תהליכים של ספקים באמצעות ממשקי AIDL

למרות ש-SurfaceFlinger מגדיר ממשקי AIDL, תהליכי ספקים יכולים להשתמש רק בממשקי HIDL כדי לתקשר עם תהליכי framework. נדרשת כמות משמעותית של עבודה כדי להמיר ממשקי 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));

הערה: תהליכים של ספקים צריכים לקרוא ל-before לפני שהם קוראים ל-Process או ל-IPCThreadState, או לפני שהם מבצעים קריאות ל-Binder.

מדיניות SELinux

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