TextureView

המחלקות TextureView הן אובייקט תצוגה שמשלב תצוגה עם SurfaceTexture.

עיבוד באמצעות OpenGL ES

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

OpenGL ES‏ (GLES) יכול לבצע רינדור ב-TextureView על ידי העברת SurfaceTexture לקריאה ליצירת EGL, אבל זה יוצר בעיה. כש-GLES מבצע רינדור ב-TextureView, יצרנים וצרכנים של BufferQueue נמצאים באותו השרשור, מה שיכול לגרום להשהיה או לכשל של קריאת החלפת המאגר. לדוגמה, אם מפיק שולח כמה מאגרי נתונים ברצף מהיר משרשור ממשק המשתמש, הפונקציה EGL buffer swap צריכה להוציא מאגר נתונים מהתור BufferQueue. עם זאת, מכיוון שהצרכן והיצרן נמצאים באותו השרשור, לא יהיו מאגרי נתונים זמניים זמינים והשיחה להחלפה תיעצר או תיכשל.

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

בחירה בין SurfaceView לבין TextureView

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

ל-TextureView יש טיפול טוב יותר באלפא ובסיבוב מאשר ל-SurfaceView, אבל ל-SurfaceView יש יתרונות בביצועים כשמבצעים קומפוזיציה של רכיבי ממשק משתמש בשכבות מעל סרטונים. כשלקוח מבצע רינדור באמצעות SurfaceView, ‏ SurfaceView מספק ללקוח שכבת קומפוזיציה נפרדת. ‫SurfaceFlinger מרכיב את השכבה הנפרדת ככיסוי חומרה אם המכשיר תומך בכך. כשלקוח מבצע רינדור באמצעות TextureView, ערכת הכלים של ממשק המשתמש יוצרת קומפוזיציה של התוכן של TextureView בהיררכיית התצוגה באמצעות ה-GPU. עדכונים בתוכן עשויים לגרום לרכיבי תצוגה אחרים לצייר מחדש, למשל אם התצוגות האחרות ממוקמות מעל TextureView. אחרי שהעיבוד של התצוגה מסתיים, SurfaceFlinger יוצר קומפוזיציה של שכבת ממשק המשתמש של האפליקציה ושל כל השכבות האחרות, כך שכל פיקסל גלוי עובר קומפוזיציה פעמיים.

מקרה לדוגמה: הפעלת סרטון של Grafika

Grafika's Play Video כולל זוג נגני וידאו, אחד עם הטמעה של TextureView ואחד עם הטמעה של SurfaceView. החלק של פענוח הווידאו בפעילות שולח פריימים מ-MediaCodec אל Surface גם ל-TextureView וגם ל-SurfaceView. ההבדל הגדול ביותר בין ההטמעות הוא השלבים שנדרשים כדי להציג את יחס הגובה-רוחב הנכון.

כדי לשנות את הגודל של SurfaceView, צריך להטמיע באופן מותאם אישית את FrameLayout. ‫WindowManager צריך לשלוח ל-SurfaceFlinger מיקום חדש של החלון וערכים חדשים של הגודל. כדי לשנות את הגודל של SurfaceTexture ב-TextureView, צריך להגדיר מטריצת טרנספורמציה באמצעות TextureView#setTransform().

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

מקרה לדוגמה: פענוח כפול של Grafika

Grafika's Double Decode מדגים מניפולציה של SurfaceTexture בתוך TextureView.

הפענוח הכפול של Grafika משתמש בזוג אובייקטים של TextureView כדי להציג שני סרטונים שמופעלים זה לצד זה, ומדמה אפליקציה לשיחות ועידה בווידאו. כשמשנים את כיוון המסך והפעילות מופעלת מחדש, מפענחי MediaCodec לא מפסיקים, ומדמים הפעלה של סטרימינג וידאו בזמן אמת. כדי לשפר את היעילות, הלקוח צריך להשאיר את הממשק פעיל. ה-Surface הוא נקודת אחיזה לממשק של היצרן ב-BufferQueue של SurfaceTexture. מכיוון שה-TextureView מנהל את ה-SurfaceTexture, הלקוח צריך לשמור על ה-SurfaceTexture פעיל כדי שהמשטח יישאר פעיל.

כדי לשמור את SurfaceTexture פעיל, הפונקציה Double Decode של Grafika מקבלת הפניות ל-SurfaceTexture מאובייקטים של TextureView ושומרת אותן בשדה סטטי. לאחר מכן, הפונקציה Double Decode של Grafika מחזירה false מ-TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed() כדי למנוע את ההרס של SurfaceTexture. לאחר מכן, TextureView מעביר SurfaceTexture אל onSurfaceTextureDestroyed() שאפשר לשמור אותו במהלך שינוי הגדרות הפעילות. הלקוח מעביר אותו אל TextureView החדש דרך setSurfaceTexture().

כל מפענח וידאו מופעל על ידי שרשור נפרד. ה-Mediaserver שולח מאגרי נתונים עם פלט מפוענח אל SurfaceTextures, צרכני ה-BufferQueue. אובייקטים של TextureView מבצעים רינדור ופועלים בשרשור של ממשק המשתמש.

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