Android כוללת שכבת הפשטת חומרה (HAL) של HIDL לרכב, שמספקת לכידה והצגה של תמונות בשלב מוקדם מאוד בתהליך האתחול של Android, וממשיכה לפעול למשך חיי המערכת. רכיב ה-HAL כולל את מחסנית מערכת התצוגה החיצונית (EVS) ובדרך כלל משמש לתמיכה במצלמה האחורית ובתצוגות של תצוגת 360 במכוניות עם מערכות מידע ובידור (IVI) מבוססות-Android. בנוסף, פרוטוקול EVS מאפשר להטמיע תכונות מתקדמות באפליקציות של משתמשים.
Android כולל גם ממשק מנהל התקן ספציפי ללכידה ולהצגה של EVS (ב-/hardware/interfaces/automotive/evs/1.0). אמנם אפשר ליצור אפליקציית מצלמה אחורית על בסיס שירותי המצלמה והתצוגה הקיימים ב-Android, אבל סביר להניח שהאפליקציה תפעל מאוחר מדי בתהליך האתחול של Android. שימוש ב-HAL ייעודי מאפשר ליצור ממשק יעיל וברור, שמסביר ליצרני ציוד מקורי (OEM) מה צריך ליישם כדי לתמוך במערך EVS.
רכיבי המערכת
מערכת EVS כוללת את רכיבי המערכת הבאים:
אפליקציית EVS
אפליקציית EVS לדוגמה ב-C++ (/packages/services/Car/evs/app) משמשת כהטמעה לדוגמה. האפליקציה הזו אחראית לבקשת פריימים של סרטונים ממנהל מערכת התצוגה החיצונית (EVS) ולשליחת פריימים מוכנים להצגה בחזרה למנהל מערכת התצוגה החיצונית.
הוא אמור להתחיל על ידי init ברגע ש-EVS ושירות הרכב זמינים, תוך שתי שניות מהפעלת המכשיר. יצרני ציוד מקורי יכולים לשנות או להחליף את אפליקציית 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()). אם מתקבלים מאגרי נתונים (buffers) שלא הוצגו בשיחות עם 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 פריימים לשנייה לפחות. הזמן שנדרש להפעלת זרם הווידאו נספר כחלק מהזמן שנדרש להפעלת המצלמה האחורית. אם השידור לא מופעל, צריך להחזיר קוד שגיאה. אחרת, צריך להחזיר 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 יכולה לבחור לאחסן כאן את הטיפול ב-buffer של Gralloc.
IEvsCameraStream
הלקוח מטמיע את הממשק הזה כדי לקבל מסגרות וידאו אסינכרוניות.
deliverFrame(BufferDesc buffer);
מקבל קריאות מה-HAL בכל פעם שמסגרת וידאו מוכנה לבדיקה.
צריך להחזיר את ה-buffer שמתקבל באמצעות ה-method הזו דרך קריאות ל-IEvsCamera::doneWithFrame(). כשזרם הווידאו מופסק באמצעות קריאה ל-IEvsCamera::stopVideoStream(), יכול להיות שהקריאה החוזרת הזו תמשיך בזמן שהצינור מתרוקן. עדיין צריך להחזיר כל פריים. כשפריים הסיום
בזרם נמסר, נמסר ערך NULL bufferHandle, שמציין את סוף הזרם ולא יתבצעו יותר מסירות של פריים. לא צריך להחזיר את הערך NULL
bufferHandle עם
doneWithFrame(), אבל צריך להחזיר את כל שאר ה-handle
אפשר להשתמש בפורמטים קנייניים של מאגרים, אבל כדי לבדוק תאימות, המאגר צריך להיות באחד מארבעת הפורמטים הנתמכים הבאים: 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 מאפשר גישה משותפת למצלמות. אפליקציית EVS ראשית אחת היא הלקוח הראשון של EVS Manager, והיא הלקוח היחיד שמורשה לכתוב נתוני תצוגה (אפשר לתת ללקוחות נוספים הרשאת קריאה בלבד לתמונות מהמצלמה).
מנהל ה-EVS מטמיע את אותו API כמו מנהלי ההתקנים הבסיסיים של HAL, ומספק שירות מורחב על ידי תמיכה בכמה לקוחות בו-זמנית (יותר מלקוח אחד יכול לפתוח מצלמה דרך מנהל ה-EVS ולקבל סטרימינג של וידאו).
האפליקציות לא רואות הבדלים כשמפעילים אותן דרך ההטמעה של 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 ועם שכבת הפשטת חומרה לרכב כדי לספק פונקציות בסיסיות של מצלמת רוורס. האפליקציה אמורה להתחיל לפעול בשלב מוקדם מאוד בתהליך האתחול של המערכת, ולהציג סרטון מתאים בהתאם למצלמות הזמינות ולמצב הרכב (הילוך ומצב איתות). יצרני ציוד מקורי יכולים לשנות את אפליקציית ה-EVS או להחליף אותה באפליקציה משלהם עם לוגיקה וממשק שמותאמים לרכב ספציפי.
מכיוון שנתוני התמונה מוצגים לאפליקציה במאגר גרפיקה סטנדרטי, האפליקציה אחראית להעברת התמונה ממאגר המקור למאגר הפלט. הפעולה הזו אמנם כרוכה בעלות של העתקת נתונים, אבל היא גם מאפשרת לאפליקציה לעבד את התמונה לתוך מאגר התצוגה בכל דרך שהיא רוצה.
לדוגמה, האפליקציה יכולה לבחור להעביר את נתוני הפיקסלים עצמם, אולי עם פעולת שינוי גודל או סיבוב מוטבעת. האפליקציה יכולה גם לבחור להשתמש בתמונת המקור כטקסטורת 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, למעט הוספה של שני שדות:
namevendor_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/app עם ממשקי 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));
הערה: תהליכים של ספקים צריכים להפעיל את הפונקציה הזו לפני הפעלת 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
עם זאת, הוספת ההרשאות האלה גורמת לכשל ב-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;