إنشاء نفق للوسائط المتعددة

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

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

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

الخلفية

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

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

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

لكي يعرض "العرض المتقدّم للوسائط" لقطات الفيديو بشكل متزامن مع لقطات الصوت المقابلة لها، يجب أن يفكّك ملف "واجهة برمجة التطبيقات لنظام الصوت" (HAL) عنوان المزامنة ويستخدم الطابع الزمني للعرض لإعادة مزامنة ساعة التشغيل مع عرض الصوت. لإعادة المزامنة عند تشغيل ملف صوتي مضغوط، قد يحتاج Audio HAL إلى تحليل البيانات الوصفية داخل بيانات الملف الصوتي المضغوط لتحديد مدة تشغيله.

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

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

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

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

اقتراحات بشأن التنفيذ

Audio HAL

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

بالنسبة إلى نظام التشغيل Android 10 أو الإصدارات الأقدم، يجب أن تتضمّن الأجهزة التي تتيح تشغيل الفيديوهات عبر النفق ملف audio_policy.conf يحتوي على ملف شخصي واحد على الأقل لبث الصوت يتضمّن علامتَي FLAG_HW_AV_SYNC و AUDIO_OUTPUT_FLAG_DIRECT. تُستخدَم هذه العلامات لضبط ساعة النظام من الساعة الصوتية.

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