Android מכיל שכבת הפשטה לחומרה (HAL) של HIDL לכלי רכב, שמאפשרת לצלם תמונות ולהציג אותן בשלב מוקדם מאוד בתהליך האתחול של Android, וממשיכה לפעול כל עוד המערכת פועלת. ה-HAL כולל את מחסנית מערכת התצוגה החיצונית (EVS), ובדרך כלל משמש לתמיכה במצלמת הרכב האחורית ובתצוגות של תצוגת היקף הרכב ברכב עם מערכות מידע ובידור ברכב (IVI) מבוססות-Android. EVS מאפשר גם להטמיע תכונות מתקדמות באפליקציות של משתמשים.
ב-Android יש גם ממשק נהג ספציפי לצילום ולהצגת מסך ספציפי לרכב חשמלי (ב-/hardware/interfaces/automotive/evs/1.0
). למרות שאפשר לבנות אפליקציה של מצלמה אחורית על גבי שירותים קיימים של מצלמה ותצוגה ב-Android, סביר להניח שאפליקציה כזו תרוץ מאוחר מדי בתהליך ההפעלה של Android. השימוש ב-HAL ייעודי מאפשר ממשק יעיל ומבהיר ליצרני ציוד מקורי מה הם צריכים להטמיע כדי לתמוך ב-EVS stack.
רכיבי המערכת
EVS כולל את רכיבי המערכת הבאים:
איור 1. סקירה כללית של רכיבי המערכת של EVS.
אפליקציית EVS
אפליקציית EVS לדוגמה ב-C++ (/packages/services/Car/evs/app
) משמשת כהטמעה לדוגמה. האפליקציה הזו אחראית לבקש מסגרות וידאו ממנהל ה-EVS ולשלוח מסגרות מוגמרות להצגה בחזרה למנהל ה-EVS.
ההפעלה של השירות צפויה להתחיל ברגע שכלי הרכב החשמליים (EVS) ושירות המכוניות זמינים, ומטורגטים תוך שתי (2) שניות מהפעלה. יצרני ציוד מקורי יכולים לשנות או להחליף את האפליקציה של הרכבים החשמליים בהתאם לצורך.
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 נדרשים מנהלי התקנים של ליבה. במקום ליצור מנהלי התקנים חדשים, יצרני ציוד מקורי יכולים לתמוך בתכונות הנדרשות ל-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
, הסטרימינג ידלג על פריימים עד שתוחזר מאגרית לשימוש חוזר. היא חוקית בכל שלב, גם בזמן שהשידורים כבר פועלים, במקרה כזה צריך להוסיף או להסיר מאגר נתונים זמני מהרשת בהתאם לצורך. אם לא מתבצעת קריאה לנקודת הכניסה הזו, IEvs Camera תומך לפחות בפריים אחד כברירת מחדל.
אם לא ניתן להכיל את הערך המבוקש של bufferCount, הפונקציה מחזירה את הערך BUFFER_NOT_AVAILABLE
או קוד שגיאה רלוונטי אחר. במקרה כזה, המערכת ממשיכה לפעול עם הערך שהוגדר קודם.
startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);
בקשה להעברת פריימים של מצלמת EVS מהמצלמה הזו. האירוע IEvsCameraStream מתחיל לקבל קריאות תקופתיות עם מסגרות תמונה חדשות עד שמתבצעת קריאה ל-stopVideoStream()
. הפריימים חייבים להתחיל להישלח תוך 500 אלפיות השנייה ממועד הקריאה ל-startVideoStream
, ולאחר ההתחלה הם חייבים להיווצר בקצב של לפחות 10FPS. הזמן הנדרש להפעלת סטרימינג של וידאו נספר כחלק מכל דרישה לזמן הפעלה של מצלמת הרכב. אם
השידור לא מתחיל, צריך להחזיר קוד שגיאה. אחרת, מוחזר תקין.
oneway doneWithFrame(BufferDesc buffer);
הפונקציה מחזירה פריים שנשלח על ידי IEvsCameraStream. בסיום צריכת הפריים שמועברת לממשק של IEvs CameraStream, צריך להחזיר את הפריים ל-IEvs Camera לשימוש חוזר. יש מספר קטן ומוגבל של מאגרי נתונים זמינים (יכול להיות שרק אחד), ואם המלאי נגמר, לא יישלחו עוד פריימים עד שיוחזר מאגר נתונים, וכתוצאה מכך יכול להיות שפריימים יימחקו (מאגר נתונים עם אחיזה 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 כדי לאפשר זיהוי של מאגר נתונים זמני אחרי מעבר הלוך ושוב דרך ממשקי HAL API. הערך ששמור בשדה הזה עשוי להיבחר באופן שרירותי על ידי הטמעת ה-HAL.memHandle
. ה-handle של מאגר הזיכרון הבסיסי שמכיל את נתוני התמונה. יכול להיות שהטמעת HAL תבחר לאחסן כאן כינוי למאגר נתונים זמני של Gralloc.
IEvsCameraStream
הלקוח מטמיע את הממשק הזה כדי לקבל העברות אסינכררוניות של פריימים של וידאו.
deliverFrame(BufferDesc buffer);
מקבלת קריאות מ-HAL בכל פעם שפריים של סרטון מוכן לבדיקה.
כדי להחזיר כינויים של מאגרי נתונים זמניים שמתקבלים בשיטה הזו, צריך להחזיר אותם בקריאות ל-IEvsCamera::doneWithFrame()
. כששידור הווידאו מושהה באמצעות קריאה ל-IEvsCamera::stopVideoStream()
, יכול להיות שהקריאה החוזרת (callback) הזו תמשיך עד שצינור עיבוד הנתונים ינוקז. עדיין צריך להחזיר כל פריים, כך שהפריים האחרון בשידור יועבר, מאגר נתונים זמני של 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 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 הנדרשים להטמעת 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 ולקבל סטרימינג של וידאו).
לאפליקציות אין הבדל בין הפעלה באמצעות הטמעת 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 Manager זהות לאלו שמדווחות לשכבת החומרה של כלי הרכב החשמלי (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 לא יכול להבין את ההשלכות של מילות בקרה שהוגדרו על ידי הספק, הן לא עוברות וירטואליזציה וכל ההשפעות הצדדיות חלות על כל הלקוחות של מצלמה נתונה. לדוגמה, אם ספק השתמש בקריאה הזו כדי לשנות את שיעורי הפריימים, כל הלקוחות של המצלמה בשכבת החומרה המושפעת יקבלו פריימים בקצב החדש.
רשת המדיה 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 מצפה לשדה הזה, שהוסרה, ולכן הוא לא מצליח לנתח את החבילה. המצב הזה יתועד ככישלון 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 בין תהליכי framework ואפליקציה עם ממשקי 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;