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

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

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

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

الخلفية

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

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

يوضّح الرسم البياني التالي كيف يسهّل التوجيه عملية تشغيل الفيديو.

مقارنة بين الوضعَين التقليدي والنفق

الشكل 1. مقارنة بين عمليات تشغيل الفيديو التقليدية وعمليات تشغيل الفيديو عبر الأنفاق

لمطوّري التطبيقات

بما أنّ معظم مطوّري التطبيقات يدمجون مكتبة لتنفيذ التشغيل، لا يتطلّب التنفيذ في معظم الحالات سوى إعادة ضبط إعدادات هذه المكتبة لتفعيل التشغيل عبر الأنفاق. لتنفيذ مشغّل فيديو بنظام الأنفاق على مستوى منخفض، اتّبِع التعليمات التالية.

لتشغيل الفيديو عند الطلب على الإصدار 5 من نظام التشغيل Android أو الإصدارات الأحدث، اتّبِع الخطوات التالية:

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

OMX

يجب أن يتوافق مكوّن برنامج الترميز الذي يتم نقله عبر قناة مع ما يلي:

  • ضبط المَعلمة OMX.google.android.index.configureVideoTunnelMode extended التي تستخدم البنية 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)

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

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

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

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

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

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

طبقة تجريد الأجهزة الصوتية (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>

Hardware Composer (HWC)

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

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

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

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

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