الاتصال النفقي للوسائط المتعددة

يتيح توسيع نطاق الوسائط المتعددة إمكانية نقل بيانات الفيديو المضغوطة من خلال تكنولوجيا برمجية لفك ترميز الفيديو مباشرةً إلى الشاشة، بدون معالجتها من خلال رمز التطبيق أو رمز إطار عمل Android. ويحدّد الرمز الخاص بالجهاز أسفل حزمة Android إطارات الفيديو التي يجب إرسالها إلى الشاشة ووقت إرسالها من خلال مقارنة الطوابع الزمنية للعرض التقديمي لإطار الفيديو بأحد أنواع الساعة الداخلية التالية:

  • لتشغيل الفيديو عند الطلب في الإصدار 5 من نظام التشغيل Android أو الإصدارات الأحدث، يجب أن يتضمّن التطبيق AudioTrack ساعة متزامنة مع الطوابع الزمنية للعرض الصوتي تم ضبطها من قِبل التطبيق.

  • لتشغيل البث المباشر على نظام Android 11 أو الإصدارات الأحدث، يجب استخدام ساعة مرجعية للبرنامج (PCR) أو ساعة زمنية للنظام (STC) تعمل من خلال موالف

خلفية

عند تشغيل الفيديوهات بالطريقة التقليدية على Android، يُرسِل التطبيق إشعارًا عند فك ترميز إطار فيديو مضغوط. بعد ذلك، يقوم التطبيق بإرسال إطار الفيديو الذي تم فك تشفيره إلى الشاشة لعرضه في توقيت الساعة النظامي نفسه الذي تم عرض إطار الصوت المقابل فيه، ويُجري التطبيق عمليات استرجاع لحالات AudioTimestamps السابقة من أجل احتساب التوقيت الصحيح.

بما أنّ تشغيل الفيديو عبر النفق يتجاوز رمز التطبيق ويقلل من عدد العمليات التي تؤثر في الفيديو، يمكن أن يقدّم عرضًا أكثر كفاءة للفيديو، وذلك استنادًا إلى طريقة تنفيذ المصنّع الأصلي للجهاز. ويمكن أن يوفّر أيضًا اتساقًا وتزامنًا أكثر دقة للفيديو مع الساعة المحدّدة (PRC أو STC أو الصوت) من خلال تجنُّب مشاكل التوقيت التي تحدث بسبب الانحراف المحتمل بين توقيت طلبات Android لعرض الفيديو وتوقيت عمليات المزامنة الفعلية للأجهزة. مع ذلك، يمكن أن يؤدي إنشاء النفق أيضًا إلى تقليل التوافق مع تأثيرات وحدة معالجة الرسومات، مثل التمويه أو الزوايا المستديرة في نوافذ ميزة "نافذة ضمن النافذة" (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.

يظهر مسار طلب بيانات من واجهة برمجة التطبيقات في مقتطفات الرمز التالية:

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.

    • في حال عدم ضبط هذه المَعلمة، يحدِّد المصنّع الأصلي للجهاز سلوكه.

  • عندما لا يتم تقديم بيانات الصوت إلى 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 مقبض نطاق جانبي يمكن استخدامه لربط برنامج الترميز بطبقة Device 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;

إذا كان المكوِّن متوافقًا مع هذه الإعدادات، يجب تخصيص مقبض نطاق جانبي لبرنامج الترميز هذا وتمريره مرة أخرى إلى عضو pSidebandWindow حتى يتمكّن HWC من التعرّف على برنامج الترميز المرتبط به. وإذا كان المكوِّن لا يتيح هذا الإعداد، يجب ضبط bTunneled على OMX_FALSE.

Codec2

في نظام التشغيل Android 11 أو الإصدارات الأحدث، يتيح Codec2 التشغيل النفقي. يجب أن يتيح مكوّن فك الترميز ما يلي:

  • جارٍ ضبط C2PortTunneledModeTuning، الذي تضبط وضع النفق ويمرر في HW_AV_SYNC الذي تم استرداده من جهاز إخراج الصوت أو من إعدادات الموالف.

  • طلب البحث من C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE لتخصيص واسترداد معرّف قناة الالتفاف لميزة "المعالجة المحدودة للطاقة"

  • التعامل مع 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 كي تتمكّن وحدة التحكّم في الأجهزة من تحديد برنامج الترميز المرتبط.

طبقة تجريد الأجهزة (HAL) للصوت

لتشغيل فيديو عند الطلب، يتلقّى "HAL" الصوت الطوابع الزمنية "للعرض الصوتي" المضمّنة مع البيانات الصوتية بتنسيق كبير داخل عنوان موجود في بداية كل جزء من البيانات الصوتية التي يكتبها التطبيق:

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 للصوت رأس المزامنة وتستخدم الطابع الزمني للعرض التقديمي لإعادة مزامنة ساعة التشغيل مع عرض الصوت. لإعادة المزامنة عند تشغيل ملف صوتي مضغوط، قد يحتاج Audio HAL إلى تحليل البيانات الوصفية داخل بيانات الملف الصوتي المضغوط لتحديد مدة تشغيله.

إيقاف الدعم مؤقتًا

لا يوفِّر الإصدار 5 من نظام التشغيل Android أو الإصدارات الأقدم إمكانية الإيقاف المؤقت. لا يمكنك إيقاف تشغيل المحتوى المُرسَل عبر النفق مؤقتًا إلا من خلال إيقاف الصوت والصورة، ولكن إذا كان المخزن المؤقت الداخلي للفيديو كبيرًا (على سبيل المثال، تتوفّر ثانية واحدة من البيانات في مكوّن OMX)، سيبدو أنّ ميزة الإيقاف المؤقت لا تستجيب.

في الإصدار 5.1 من نظام التشغيل Android أو الإصدارات الأحدث، تتيح AudioFlinger إيقاف الصوت مؤقتًا واستئنافه في مخرجات الصوت المباشرة (المُشفَّرة). إذا نفذت HAL الإيقاف المؤقت والاستئناف، تتم إعادة توجيه تتبع الإيقاف المؤقت والسيرة الذاتية إلى HAL.

تتم مراعاة الإيقاف المؤقت والتدفق واستئناف المكالمات من خلال تنفيذ استدعاءات HAL في سلسلة التشغيل (نفس التحميل).

اقتراحات التنفيذ

Audio HAL

بالنسبة إلى نظام التشغيل Android 11، يمكن استخدام معرّف مزامنة الأجهزة من PCR أو STC لمزامنة الصوت والصورة، وبالتالي يمكن بث الفيديو فقط.

في نظام التشغيل 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>

أداة إنشاء الأجهزة (HWC)

عند توفُّر طبقة نفقية (طبقة تحتوي على HWC_SIDEBAND compositionType) على الشاشة، يكون sidebandStream الخاص بالطبقة هو مقبض النطاق الجانبي الذي يخصّصه مكوِّن فيديو OMX.

تعمل واجهة HWC على مزامنة إطارات الفيديو التي تم فك ترميزها (من مكوّن OMX الذي تم انشاؤه في نفق) مع المسار الصوتي المرتبط (الذي يحمل معرّف audio-hw-sync). عندما يصبح إطار فيديو جديد حاليًا، يُدمجه "العرض التقديمي بدقة عالية" مع المحتوى الحالي لجميع الطبقات التي تم استلامها خلال آخر مكالمة إعداد أو إعداد، ويعرض الصورة الناتجة. لا تتم عمليات التجهيز أو الضبط إلا عند تغيير الطبقات الأخرى، أو عندما تتغير خصائص طبقة النطاق الجانبي (مثل الموضع أو الحجم).

يمثّل الشكل التالي وحدة HWC التي تعمل مع أداة مزامنة الأجهزة (أو النواة أو البرنامج المشغِّل) لدمج لقطات الفيديو (7b) مع أحدث تركيبة (7a) لعرضها في الوقت الصحيح استنادًا إلى الصوت (7c).

ميزة &quot;التجميع الذكي للصور&quot; (HWC) التي تجمع لقطات الفيديو استنادًا إلى الصوت

الشكل 2. أداة مزامنة أجهزة HWC (أو النواة أو برنامج التشغيل)