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

يمكّن نفق الوسائط المتعددة بيانات الفيديو المضغوطة من المرور عبر وحدة فك ترميز فيديو الأجهزة مباشرةً إلى شاشة العرض، دون معالجتها بواسطة رمز التطبيق أو رمز إطار عمل 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 )، فهذا يعني أن إطار الفيديو الأول المعروض يجب أن يكون دائمًا إطار I.

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

الترميز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;
}

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

الصوت هال

لتشغيل الفيديو عند الطلب، يتلقى 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 في سلسلة التشغيل (مثل إلغاء التحميل).

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

الصوت هال

بالنسبة لنظام التشغيل Android 11، يمكن استخدام معرف مزامنة HW من PCR أو STC لمزامنة الصوت/الفيديو، لذلك يتم دعم دفق الفيديو فقط.

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

أو إم إكس

يجب أن يكون لدى الشركات المصنعة للأجهزة مكون 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)

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

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

يمثل الشكل التالي HWC الذي يعمل مع جهاز المزامنة (أو النواة أو برنامج التشغيل)، لدمج إطارات الفيديو (7ب) مع أحدث تكوين (7أ) لعرضه في الوقت الصحيح، بناءً على الصوت (7ج).

HWC يجمع بين إطارات الفيديو بناءً على الصوت

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