מנהור מולטימדיה

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

  • להפעלת וידאו על פי דרישה ב-Android מגרסה 5 ואילך, שעון AudioTrack שמסתנכרן עם חותמות הזמן של הצגת האודיו שנשלחו על ידי האפליקציה

  • להפעלת שידור חי ב-Android מגרסה 11 ואילך, שעון הפניה לתוכנית (PCR) או שעון זמן מערכת (STC) שמופעל על ידי מקלט

רקע

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

מכיוון שהפעלת וידאו במנהרה עוקפת את קוד האפליקציה ומפחיתה את מספר התהליכים שפועלים על הסרטון, היא יכולה לספק עיבוד וידאו יעיל יותר, בהתאם להטמעה של יצרן הציוד המקורי (OEM). הוא גם יכול לספק קצב וידאו ותזמון מדויקים יותר לשעון שנבחר (PRC,‏ STC או אודיו), על ידי הימנעות מבעיות תזמון שנובעות מהטיה פוטנציאלית בין התזמון של הבקשות של Android לעיבוד וידאו לבין התזמון של vsyncs חומרה אמיתיים. עם זאת, מנהרה יכולה גם לצמצם את התמיכה באפקטים של GPU, כמו טשטוש או פינות מעוגלות בחלונות של 'תמונה בתוך תמונה' (PiP), כי מאגרי הנתונים עוקפים את מקבץ הגרפיקה של Android.

בתרשים הבא מוצג איך תהליך הניתוב פשוט יותר בתהליך ההפעלה של הסרטון.

השוואה בין המצבים 'רגיל' ו'מנהרה'

איור 1. השוואה בין תהליכי הפעלת וידאו מסורתיים לבין תהליכי הפעלת וידאו במנהרה

למפתחי אפליקציות

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

כדי להפעיל סרטונים על פי דרישה ב-Android מגרסה 5 ואילך:

  1. יוצרים מכונה של SurfaceView.

  2. יוצרים מכונה של audioSessionId.

  3. יוצרים מכונות AudioTrack ו-MediaCodec באמצעות המכונה audioSessionId שנוצרה בשלב 2.

  4. מוסיפים נתוני אודיו לתור ב-AudioTrack עם חותמת הזמן של הצגת המסך לפריים האודיו הראשון בנתוני האודיו.

כדי להפעיל שידורים חיים ב-Android מגרסה 11 ואילך:

  1. יוצרים מכונה של SurfaceView.

  2. אחזור של מכונה של avSyncHwId מ-Tuner.

  3. יוצרים מכונות 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 לא גדל כשמוסיפים פריים אודיו שקט.

ליצרני מכשירים

הגדרות אישיות

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

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

OMX

רכיב מפענח במנהרה צריך לתמוך באפשרויות הבאות:

  • הגדרת הפרמטר המורחב OMX.google.android.index.configureVideoTunnelMode, שמשתמש במבנה 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;

אם הרכיב תומך בהגדרה הזו, הוא צריך להקצות למקודק הזה מנוף לצדדים (sideband) ולהעביר אותו חזרה דרך המאפיין 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, &params);
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.

כדי לפעול בהתאם לרצף הקריאות של השהיה, שטיפה והמשך, מריצים את הקריאות ל-HAL בשרשור ההפעלה (כמו בהעברה לענן).

הצעות להטמעה

Audio HAL

ב-Android 11, אפשר להשתמש במזהה הסנכרון של החומרה מ-PCR או מ-STC לסנכרון A/V, כך שאפשר להעביר סטרימינג של וידאו בלבד.

במכשירים עם Android מגרסה 10 ואילך שתומכים בהפעלת סרטונים במנהרה, צריך להיות לפחות פרופיל אחד של סטרימינג של פלט אודיו עם הדגלים FLAG_HW_AV_SYNC ו-AUDIO_OUTPUT_FLAG_DIRECT בקובץ audio_policy.conf. הדגלים האלה משמשים להגדרת שעון המערכת מהשעון הקולי.

OMX

ליצרני המכשירים צריך להיות רכיב OMX נפרד להפעלת וידאו במנהרה (יצרנים יכולים לכלול רכיבי OMX נוספים לסוגים אחרים של הפעלת אודיו ווידאו, כמו הפעלה מאובטחת). הרכיב שנמצא במנהרה צריך:

  • מציינים מאגרים (nBufferCountMin, nBufferCountActual) של 0 ביציאה.

  • מטמיעים את התוסף 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 בשכבה הוא ה-handle של הפס הצדדי שהוקצה על ידי רכיב הווידאו של OMX.

ה-HWC מסנכרן פריימים של וידאו שעבר פענוח (מרכיב ה-OMX במנהרה) לטראק האודיו המשויך (עם המזהה audio-hw-sync). כשפריים חדש של וידאו הופך לעדכני, ה-HWC משלב אותו עם התוכן הנוכחי של כל השכבות שהתקבלו במהלך הקריאה האחרונה של prepare או set, ומציג את התמונה שנוצרה. הקריאות prepare או set מתבצעות רק כששכבות אחרות משתנות, או כשמאפיינים של שכבת הצד (כמו המיקום או הגודל) משתנים.

בתרשים הבא מוצגת ה-HWC שפועלת עם מסנן ה-Hardware (או הליבה או הנהג) כדי לשלב בין פריימים של וידאו (7b) לבין הקומפוזיציה האחרונה (7a) להצגה בזמן הנכון, על סמך האודיו (7c).

HWC שילוב של פריימים של וידאו על סמך אודיו

איור 2. סנכרון של חומרה (או ליבה או מנהל התקן) של HWC