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

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

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

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

רקע

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

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

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

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

אודיו HAL

בהפעלת וידאו על פי דרישה, ה-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 בשרשור ההפעלה (כמו בהעברה לענן).

הצעות להטמעה

אודיו HAL

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

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

OMX

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

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

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

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

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