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

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

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

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

רקע

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

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

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

    • אם לא מגדירים את הפרמטר הזה, ה-OEM (יצרן הציוד המקורי) קובע את ההתנהגות של המכשיר.

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

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

mOutput->stream->common.set_parameters(&mOutput->stream->common, AUDIO_PARAMETER_STREAM_HW_AV_SYNC, hwAVSyncId);

המזהה של HW_AV_SYNC, תואם לשידור של פלט אודיו או של Tuner, מועברת לרכיב OMX או Codec2 כדי קוד ה-OEM יכול לשייך את הקודק לזרם המתאים של פלט האודיו, או את זרם הטיונר.

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

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;

אם הרכיב תומך בהגדרה הזו, צריך להקצות לו פס צד את ה-codec הזה ולהעביר אותו חזרה דרך החבר pSidebandWindow, שה-HWC יכול לזהות את הקודק המשויך. אם הרכיב לא תומך בהגדרה הזאת, הוא צריך להגדיר את bTunneled לערך OMX_FALSE.

קודק2

ב-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;
}

אם הרכיב תומך בהגדרה הזו, צריך להקצות לו פס צד לטפל ב-codec הזה ולהעביר אותו חזרה דרך C2PortTunnelHandlingTuning, כך שה-HWC יכול לזהות את הקודק המשויך.

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

השהיית התמיכה

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

ב-Android 5.1 ואילך, AudioFlinger תומך בהשהיה ובהמשך בהפעלה ישירה פלט אודיו (מנהור). אם הנחיות ה-HAL מטמיעות השהיה והמשך, ניתן לעקוב אחר ההשהיה וקורות החיים מועברים ל-HAL.

ביצוע קריאות HAL מכבד את רצף השיחה, השפלה וההמשך של השיחה בשרשור של ההפעלה (זהה ל-offload).

הצעות להטמעה

אודיו HAL

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

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

OMX

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

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

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

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