מנהור מולטימדיה מאפשר לנתוני וידאו דחוסים לעבור דרך מפענח וידאו של חומרה ישירות לתצוגה, בלי לעבור עיבוד על ידי קוד האפליקציה או קוד מסגרת Android. הקוד הספציפי למכשיר שמתחת ל-Android stack קובע אילו פריימים של סרטונים יישלחו לתצוגה ומתי הם יישלחו, על ידי השוואה בין חותמות הזמן של הצגת הפריימים של הסרטון לבין אחד מסוגי השעון הפנימי הבאים:
לצורך הפעלת סרטונים על פי דרישה ב-Android 5 ואילך, נדרש שעון
AudioTrack
שמסונכרן עם חותמות הזמן של הצגת האודיו שמועבר על ידי האפליקציהלהפעלת שידור חי ב-Android בגרסה 11 ואילך, נדרש שעון ייחוס לתוכנית (PCR) או שעון זמן מערכת (STC) שמבוסס על מקלט
רקע
הפעלת סרטונים מסורתית ב-Android שולחת הודעה לאפליקציה כשפריים של סרטון דחוס מפוענח. לאחר מכן, האפליקציה משחררת את פריים הסרטון המפוענח לתצוגה כדי שיוצג באותו זמן של שעון המערכת כמו פריים האודיו התואם, ומאחזרת מקרים היסטוריים של AudioTimestamps
כדי לחשב את התזמון הנכון.
הפעלת סרטונים במנהור עוקפת את קוד האפליקציה ומצמצמת את מספר התהליכים שפועלים על הסרטון, ולכן יכולה לספק עיבוד יעיל יותר של סרטונים, בהתאם להטמעה של יצרן הציוד המקורי. בנוסף, הוא יכול לספק קצב פריימים וסנכרון מדויקים יותר של הווידאו עם השעון שנבחר (PRC, STC או אודיו) על ידי הימנעות מבעיות תזמון שנובעות מהטיה פוטנציאלית בין התזמון של בקשות Android לעיבוד הווידאו לבין התזמון של סנכרון אנכי אמיתי של החומרה. עם זאת, טכנולוגיית המנהור יכולה גם להפחית את התמיכה באפקטים של GPU, כמו טשטוש או פינות מעוגלות בחלונות של תמונה בתוך תמונה (PiP), כי המאגרים עוקפים את מחסנית הגרפיקה של Android.
בתרשים הבא מוצג תהליך הפעלת הווידאו כשהוא מועבר דרך מנהור.
איור 1. השוואה בין תהליכי הפעלה של סרטונים רגילים וסרטונים שמופעלים באמצעות מנהור
למפתחי אפליקציות
מכיוון שרוב מפתחי האפליקציות משתמשים בספרייה להטמעה של הפעלה, ברוב המקרים ההטמעה דורשת רק הגדרה מחדש של הספרייה הזו להפעלה במנהור. כדי להטמיע נגן וידאו עם מנהור ברמה נמוכה, פועלים לפי ההוראות הבאות.
כדי להפעיל סרטונים על פי דרישה ב-Android 5 ומעלה:
יוצרים מכונת
SurfaceView
.יוצרים מכונת
audioSessionId
.יוצרים מכונות
AudioTrack
ו-MediaCodec
עם המכונהaudioSessionId
שנוצרה בשלב 2.מעבירים נתוני אודיו לתור של
AudioTrack
עם חותמת הזמן של המצגת עבור הפריים הראשון של האודיו בנתוני האודיו.
כדי להפעיל שידור חי ב-Android מגרסה 11 ואילך:
יוצרים מכונת
SurfaceView
.קבלת מופע של
avSyncHwId
מ-Tuner
.יוצרים מכונות
AudioTrack
ו-MediaCodec
עם מכונתavSyncHwId
שנוצרה בשלב 2.
קטעי הקוד הבאים מציגים את תהליך הקריאה ל-API:
aab.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE);
// configure for audio clock sync
aab.setFlag(AudioAttributes.FLAG_HW_AV_SYNC);
// or, for tuner clock sync (Android 11 or higher)
new tunerConfig = TunerConfiguration(0, avSyncId);
aab.setTunerConfiguration(tunerConfig);
if (codecName == null) {
return FAILURE;
}
// configure for audio clock sync
mf.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, audioSessionId);
// or, for tuner clock sync (Android 11 or higher)
mf.setInteger(MediaFormat.KEY_HARDWARE_AV_SYNC_ID, avSyncId);
התנהגות של הפעלת וידאו על פי דרישה
הפעלת וידאו לפי דרישה במנהור קשורה באופן מרומז להפעלה של AudioTrack
הסרטון, ולכן אופן הפעולה של הפעלת וידאו במנהור עשוי להיות תלוי באופן הפעולה
של הפעלת אודיו.
ברוב המכשירים, כברירת מחדל, פריים של סרטון לא מוצג עד שהפעלת האודיו מתחילה. עם זאת, יכול להיות שהאפליקציה תצטרך להציג פריים של וידאו לפני שתתחיל להפעיל את האודיו, למשל כדי להראות למשתמש את המיקום הנוכחי של הסרטון בזמן החיפוש.
כדי לציין שצריך לעבד את המסגרת הראשונה של הסרטון בתור ברגע שהיא מפוענחת, מגדירים את הפרמטר
PARAMETER_KEY_TUNNEL_PEEK
לערך1
. כשפריימים של סרטון דחוס מסודרים מחדש בתור (למשל, כשקיימים פריימים מסוג B), המשמעות היא שהפריים הראשון של הסרטון שמוצג צריך להיות תמיד פריים מסוג I.אם אתם לא רוצים שהפריים הראשון של הסרטון שנוסף לתור יוצג עד שתתחיל ההפעלה של האודיו, צריך להגדיר את הפרמטר הזה לערך
0
.אם הפרמטר הזה לא מוגדר, יצרן ה-OEM קובע את אופן הפעולה של המכשיר.
אם לא מסופקים נתוני אודיו ל-
AudioTrack
והמאגרים ריקים (חוסר באודיו), הפעלת הווידאו נעצרת עד שייכתבו עוד נתוני אודיו, כי שעון האודיו לא מתקדם יותר.במהלך ההפעלה, יכול להיות שיופיעו אי-רציפויות בחותמות הזמן של הצגת האודיו, שהאפליקציה לא יכולה לתקן. במקרים כאלה, יצרן הציוד המקורי מתקן פערים שליליים על ידי השהיית פריים הווידאו הנוכחי, ופערים חיוביים על ידי השמטת פריים הווידאו או הוספת פריים אודיו שקט (בהתאם להטמעה של יצרן הציוד המקורי). מיקום הפריימים של
AudioTimestamp
לא גדל עבור פריימים של אודיו שקט שנוספו.
ליצרני מכשירים
הגדרות אישיות
יצרני ציוד מקורי (OEM) צריכים ליצור מפענח וידאו נפרד כדי לתמוך בהפעלת וידאו בשיטת טאנלינג.
המפענח הזה צריך לפרסם שהוא מסוגל להפעיל תוכן במנהור בקובץ media_codecs.xml
:
<Feature name="tunneled-playback" required="true"/>
כשמוגדר מופע MediaCodec
עם מנהור עם מזהה של סשן אודיו, הוא שולח שאילתה אל AudioFlinger
לגבי מזהה HW_AV_SYNC
הזה:
if (entry.getKey().equals(MediaFormat.KEY_AUDIO_SESSION_ID)) {
int sessionId = 0;
try {
sessionId = (Integer)entry.getValue();
}
catch (Exception e) {
throw new IllegalArgumentException("Wrong Session ID Parameter!");
}
keys[i] = "audio-hw-sync";
values[i] = AudioSystem.getAudioHwSyncForSession(sessionId);
}
במהלך השאילתה הזו, AudioFlinger
מאחזר את מזהה HW_AV_SYNC
ממכשיר האודיו הראשי ומשייך אותו באופן פנימי למזהה של סשן האודיו:
audio_hw_device_t *dev = mPrimaryHardwareDev->hwDevice();
char *reply = dev->get_parameters(dev, AUDIO_PARAMETER_HW_AV_SYNC);
AudioParameter param = AudioParameter(String8(reply));
int hwAVSyncId;
param.getInt(String8(AUDIO_PARAMETER_HW_AV_SYNC), hwAVSyncId);
אם כבר נוצר מופע AudioTrack
, המזהה HW_AV_SYNC
מועבר לזרם הפלט עם אותו מזהה של סשן האודיו. אם הוא עדיין לא נוצר, המזהה HW_AV_SYNC
מועבר לזרם הפלט במהלך היצירה של AudioTrack
. הפעולה הזו מתבצעת על ידי השרשור של ההפעלה:
mOutput->stream->common.set_parameters(&mOutput->stream->common, AUDIO_PARAMETER_STREAM_HW_AV_SYNC, hwAVSyncId);
המזהה HW_AV_SYNC
, בין אם הוא תואם לסטרימינג של פלט אודיו או להגדרה Tuner
, מועבר לרכיב OMX או Codec2 כדי שקוד ה-OEM יוכל לשייך את הקודק לסטרימינג המתאים של פלט האודיו או לסטרימינג של הטיונר.
במהלך הגדרת הרכיב, רכיב ה-OMX או Codec2 צריך להחזיר ידית פס צד שאפשר להשתמש בה כדי לשייך את הקודק לשכבת Hardware Composer (HWC). כשהאפליקציה משייכת משטח ל-MediaCodec
, ה-handle של פס הצד הזה מועבר ל-HWC דרך SurfaceFlinger
, שמגדיר את השכבה כשכבת פס צד.
err = native_window_set_sideband_stream(nativeWindow.get(), sidebandHandle);
if (err != OK) {
ALOGE("native_window_set_sideband_stream(%p) failed! (err %d).", sidebandHandle, err);
return err;
}
ה-HWC אחראי לקבלת מאגרי תמונות חדשים מפלט ה-codec בזמן המתאים, מסונכרנים לזרם פלט האודיו המשויך או לשעון הייחוס של תוכנית הטיונר, להרכבת המאגרים עם התוכן הנוכחי של שכבות אחרות ולהצגת התמונה שמתקבלת. הפעולה הזו מתבצעת בנפרד מהמחזור הרגיל של הכנה והגדרה. הקריאות להכנה ולהגדרה מתבצעות רק כששכבות אחרות משתנות, או כשמאפיינים של שכבת הפס הצדדי (כמו מיקום או גודל) משתנים.
OMX
רכיב של מפענח מנהור צריך לתמוך בפעולות הבאות:
הגדרת הפרמטר
OMX.google.android.index.configureVideoTunnelMode
extended, שמשתמש במבנהConfigureVideoTunnelModeParams
כדי להעביר את המזההHW_AV_SYNC
שמשויך למכשיר פלט האודיו.הגדרת הפרמטר
OMX_IndexConfigAndroidTunnelPeek
שאומר לקודק להציג או לא להציג את פריים הווידאו המפוענח הראשון, בלי קשר לשאלה אם הפעלת האודיו התחילה.שליחת האירוע
OMX_EventOnFirstTunnelFrameReady
כשמסגרת הווידאו הראשונה שמועברת במנהור מפוענחת ומוכנה להצגה.
ההטמעה של AOSP מגדירה את מצב המנהור ב-ACodec
דרך OMXNodeInstance
, כמו שמוצג בקטע הקוד הבא:
OMX_INDEXTYPE index;
OMX_STRING name = const_cast<OMX_STRING>(
"OMX.google.android.index.configureVideoTunnelMode");
OMX_ERRORTYPE err = OMX_GetExtensionIndex(mHandle, name, &index);
ConfigureVideoTunnelModeParams tunnelParams;
InitOMXParams(&tunnelParams);
tunnelParams.nPortIndex = portIndex;
tunnelParams.bTunneled = tunneled;
tunnelParams.nAudioHwSync = audioHwSync;
err = OMX_SetParameter(mHandle, index, &tunnelParams);
err = OMX_GetParameter(mHandle, index, &tunnelParams);
sidebandHandle = (native_handle_t*)tunnelParams.pSidebandWindow;
אם הרכיב תומך בהגדרה הזו, הוא צריך להקצות לרכיב הקודק הזה נקודת אחיזה של ערוץ צדדי ולהעביר אותה חזרה דרך חבר pSidebandWindow
כדי שמנהל ה-HWC יוכל לזהות את רכיב הקודק המשויך. אם הרכיב לא תומך בהגדרה הזו, הוא צריך להגדיר את bTunneled
ל-OMX_FALSE
.
Codec2
ב-Android מגרסה 11 ואילך, Codec2
תומך בהפעלה במנהור. רכיב הפענוח צריך לתמוך בפעולות הבאות:
הגדרת
C2PortTunneledModeTuning
, שקובעת את מצב המנהור ומעבירה אתHW_AV_SYNC
שאוחזר ממכשיר פלט האודיו או מהגדרת הטיונר.שאילתה של
C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE
, כדי להקצות ולאחזר את נקודת האחיזה של הערוץ הצדדי עבור HWC.טיפול ב-
C2_PARAMKEY_TUNNEL_HOLD_RENDER
כשהוא מצורף ל-C2Work
, שמורה לקודק לפענח ולסמן את סיום העבודה, אבל לא לעבד את מאגר הפלט עד ש-1) הקודק יקבל בהמשך הוראה לעבד אותו או 2) הפעלת האודיו תתחיל.טיפול ב-
C2_PARAMKEY_TUNNEL_START_RENDER
, שמורה לקודק להציג באופן מיידי את הפריים שסומן ב-C2_PARAMKEY_TUNNEL_HOLD_RENDER
, גם אם הפעלת האודיו לא התחילה.לא להגדיר את
debug.stagefright.ccodec_delayed_params
(מומלץ). אם מגדירים אותה, צריך להגדיר אותה לערךfalse
.
ההטמעה של AOSP מגדירה את מצב המנהור ב-CCodec
דרך C2PortTunnelModeTuning
, כמו שמוצג בקטע הקוד הבא:
if (msg->findInt32("audio-hw-sync", &tunneledPlayback->m.syncId[0])) {
tunneledPlayback->m.syncType =
C2PortTunneledModeTuning::Struct::sync_type_t::AUDIO_HW_SYNC;
} else if (msg->findInt32("hw-av-sync-id", &tunneledPlayback->m.syncId[0])) {
tunneledPlayback->m.syncType =
C2PortTunneledModeTuning::Struct::sync_type_t::HW_AV_SYNC;
} else {
tunneledPlayback->m.syncType =
C2PortTunneledModeTuning::Struct::sync_type_t::REALTIME;
tunneledPlayback->setFlexCount(0);
}
c2_status_t c2err = comp->config({ tunneledPlayback.get() }, C2_MAY_BLOCK,
failures);
std::vector<std::unique_ptr<C2Param>> params;
c2err = comp->query({}, {C2PortTunnelHandleTuning::output::PARAM_TYPE},
C2_DONT_BLOCK, ¶ms);
if (c2err == C2_OK && params.size() == 1u) {
C2PortTunnelHandleTuning::output *videoTunnelSideband =
C2PortTunnelHandleTuning::output::From(params[0].get());
return OK;
}
אם הרכיב תומך בהגדרה הזו, הוא צריך להקצות לרכיב הקודק הזה נקודת אחיזה של ערוץ צדדי ולהעביר אותה בחזרה דרך C2PortTunnelHandlingTuning
כדי שרכיב ה-HWC יוכל לזהות את רכיב הקודק המשויך.
Audio HAL
להפעלת סרטון על פי דרישה, ה-Audio HAL מקבל את חותמות הזמן של הצגת האודיו בשורה עם נתוני האודיו בפורמט big-endian בתוך כותרת שנמצאת בתחילת כל בלוק של נתוני אודיו שהאפליקציה כותבת:
struct TunnelModeSyncHeader {
// The 32-bit data to identify the sync header (0x55550002)
int32 syncWord;
// The size of the audio data following the sync header before the next sync
// header might be found.
int32 sizeInBytes;
// The presentation timestamp of the first audio sample following the sync
// header.
int64 presentationTimestamp;
// The number of bytes to skip after the beginning of the sync header to find the
// first audio sample (20 bytes for compressed audio, or larger for PCM, aligned
// to the channel count and sample size).
int32 offset;
}
כדי ש-HWC יעבד פריימים של סרטונים בסנכרון עם הפריימים התואמים של האודיו, ה-Audio HAL צריך לנתח את כותרת הסנכרון ולהשתמש בחותמת הזמן של ההצגה כדי לסנכרן מחדש את שעון ההפעלה עם עיבוד האודיו. כדי לבצע סנכרון מחדש בזמן השמעה של אודיו דחוס, יכול להיות ששכבת ה-HAL של האודיו תצטרך לנתח מטא-נתונים בתוך נתוני האודיו הדחוס כדי לקבוע את משך ההפעלה שלו.
השהיית התמיכה
ב-Android בגרסה 5 ומטה אין תמיכה בהשהיה. אפשר להשהות הפעלה של מנהור רק על ידי הרעבת A/V, אבל אם המאגר הפנימי של הווידאו גדול (לדוגמה, יש שנייה אחת של נתונים ברכיב OMX), ההשהיה נראית לא מגיבה.
ב-Android 5.1 ואילך, AudioFlinger
תומך בהשהיה ובהמשך הפעלה של פלט אודיו ישיר (מנהור). אם שכבת ה-HAL מטמיעה השהיה והפעלה מחדש, השהיה
והפעלה מחדש של הרצועה מועברות לשכבת ה-HAL.
רצף הקריאות pause, flush, resume מבוצע על ידי הפעלת קריאות HAL בשרשור ההפעלה (בדומה להעברה).
הצעות להטמעה
Audio HAL
ב-Android 11, אפשר להשתמש במזהה הסנכרון של החומרה מ-PCR או מ-STC לסנכרון אודיו ווידאו, כך שנתמך סטרימינג של וידאו בלבד.
ב-Android מגרסה 10 ומטה, למכשירים שתומכים בהפעלת סרטונים בשיטת tunneled צריכים להיות לפחות פרופיל אחד של זרם פלט אודיו עם הדגלים FLAG_HW_AV_SYNC
ו-AUDIO_OUTPUT_FLAG_DIRECT
בקובץ audio_policy.conf
. הדגלים האלה משמשים להגדרת שעון המערכת משעון האודיו.
OMX
ליצרני מכשירים צריך להיות רכיב OMX נפרד להפעלת סרטונים בשיטת ה-tunneling (יצרנים יכולים להשתמש ברכיבי OMX נוספים להפעלת אודיו ווידאו מסוגים אחרים, כמו הפעלה מאובטחת). הרכיב המנהור צריך:
מציינים 0 מאגרי נתונים זמניים (
nBufferCountMin
,nBufferCountActual
) ביציאה שלו.מטמיעים את התוסף
OMX.google.android.index.prepareForAdaptivePlayback setParameter
.מציינים את היכולות שלו בקובץ
media_codecs.xml
ומצהירים על התכונה 'הפעלה במנהרה'. בנוסף, צריך לציין את כל המגבלות על גודל המסגרת, היישור או קצב העברת הנתונים. דוגמה:<MediaCodec name="OMX.OEM_NAME.VIDEO.DECODER.AVC.tunneled" type="video/avc" > <Feature name="adaptive-playback" /> <Feature name="tunneled-playback" required=”true” /> <Limit name="size" min="32x32" max="3840x2160" /> <Limit name="alignment" value="2x2" /> <Limit name="bitrate" range="1-20000000" /> ... </MediaCodec>
אם אותו רכיב OMX משמש לתמיכה בפענוח עם מנהור ובפענוח ללא מנהור, התכונה של הפעלת תוכן עם מנהור לא צריכה להיות חובה. לכן, גם למפענחים עם מנהור וגם למפענחים ללא מנהור יש את אותן מגבלות יכולת. דוגמה:
<MediaCodec name="OMX._OEM\_NAME_.VIDEO.DECODER.AVC" type="video/avc" >
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" />
<Limit name="size" min="32x32" max="3840x2160" />
<Limit name="alignment" value="2x2" />
<Limit name="bitrate" range="1-20000000" />
...
</MediaCodec>
Hardware Composer (HWC)
כשמוצגת שכבה עם מנהור (שכבה עם HWC_SIDEBAND
compositionType
), הערך של sidebandStream
בשכבה הוא נקודת האחיזה של פס הצד שהוקצתה על ידי רכיב הווידאו של OMX.
ה-HWC מסנכרן פריימים של וידאו מפוענח (מרכיב ה-OMX המנהר) עם טראק האודיו המשויך (עם המזהה audio-hw-sync
). כשפריים חדש של סרטון הופך להיות הנוכחי, ה-HWC מרכיב אותו עם התוכן הנוכחי של כל השכבות שהתקבלו במהלך הקריאה האחרונה של prepare או set, ומציג את התמונה שמתקבלת.
הפעולות prepare או set calls מתבצעות רק כששכבות אחרות משתנות, או כשמאפיינים של שכבת ה-sideband (כמו מיקום או גודל) משתנים.
באיור הבא מוצגת פעולת ה-HWC עם מסנכרן החומרה (או הליבה או מנהל ההתקן), כדי לשלב בין פריימים של וידאו (7ב) לבין הקומפוזיציה האחרונה (7א) להצגה בזמן הנכון, על סמך האודיו (7ג).
איור 2. סנכרון חומרה של HWC (או של ליבת מערכת ההפעלה או מנהל ההתקן)