الصوت المكاني

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

على سبيل المثال، في فيلم، قد يبدأ الصوت الصادر من السيارة خلف المستخدم، ويتحرك إلى الأمام، ثم يتقدم بعيدًا. في دردشة الفيديو، يمكن فصل الأصوات ووضعها حول المستخدم، مما يسهل تحديد المتحدثين.

إذا كان المحتوى الخاص بك يستخدم تنسيق صوت متوافقًا، يمكنك إضافة صوت مكاني إلى تطبيقك بدءًا من Android 13 (المستوى 33 من واجهة برمجة التطبيقات).

طلب بحث عن الإمكانات

استخدِم الفئة Spatializer لطلب البحث عن إمكانات وسلوك الجهاز في الترتيب المكاني. يمكنك البدء باسترداد مثيل من Spatializer من AudioManager:

Kotlin

val spatializer = audioManager.spatializer

Java

Spatializer spatializer = AudioManager.getSpatializer();

بعد الحصول على Spatializer، ابحث عن الشروط الأربعة التي يجب أن تؤدي إلى ضبط الجهاز على إخراج صوت مكاني:

المعايير التحقّق
هل يتيح الجهاز ميزة تحديد المكان؟ getImmersiveAudioLevel() ليس SPATIALIZER_IMMERSIVE_LEVEL_NONE
هل تتوفّر ميزة تحديد المكان؟
يعتمد مدى التوفّر على التوافق مع التوجيه الحالي لإخراج الصوت.
isAvailable() هي true
هل ميزة تحديد المكان مفعّلة؟ isEnabled() هي true
هل يمكن استخدام ترتيب مكاني لمقطع صوتي يتضمّن المعلَمات المحدّدة؟ canBeSpatialized() هي true

وقد لا يتم استيفاء هذه الشروط، مثلاً إذا كانت ميزة تحديد المكان غير متاحة للمقطع الصوتي الحالي أو تم إيقافها على جهاز إخراج الصوت تمامًا.

تتبُّع حركة الرأس

وباستخدام سمّاعات الرأس المتوافقة، يمكن للمنصة ضبط مكان الصوت بناءً على موضع رأس المستخدم. لمعرفة ما إذا كان جهاز تتبُّع الرأس متاحًا لتوجيه إخراج الصوت الحالي، يمكنك الاتصال بالرقم isHeadTrackerAvailable().

المحتوى المتوافق

يشير Spatializer.canBeSpatialized() إلى ما إذا كان من الممكن تحديد مكان الصوت بالخصائص المحدّدة باستخدام التوجيه الحالي لجهاز الإخراج. تستخدم هذه الطريقة كلاً من AudioAttributes وAudioFormat، وكلاهما تم توضيحهما بمزيد من التفصيل أدناه.

AudioAttributes

يصف عنصر AudioAttributes استخدام البث الصوتي (مثل صوت اللعبة أو الوسائط العادية)، بالإضافة إلى سلوكيات التشغيل ونوع المحتوى.

عند الاتصال بـ canBeSpatialized()، استخدِم المثيل AudioAttributes نفسه الذي تم ضبطه في Player. على سبيل المثال، إذا كنت تستخدم مكتبة Jetpack Media3 ولم يتم تخصيص AudioAttributes، يمكنك استخدام AudioAttributes.DEFAULT.

إيقاف ميزة الصوت المكاني

للإشارة إلى أنّه سبق تحديد مكان المحتوى، يمكنك استدعاء الرمز setIsContentSpatialized(true) حتى لا تتم معالجة الصوت مرّتين. يمكنك بدلاً من ذلك ضبط سلوك تحديد المكان لإيقافه تمامًا من خلال استدعاء setSpatializationBehavior(AudioAttributes.SPATIALIZATION_BEHAVIOR_NEVER).

AudioFormat

يصف عنصر AudioFormat تفاصيل حول تنسيق المقطع الصوتي وإعدادات القناة.

عند إنشاء مثيل لـ AudioFormat لتمريره إلى canBeSpatialized()، اضبط التشفير على نفس تنسيق الإخراج المتوقع من برنامج فك الترميز. يجب أيضًا ضبط قناع قناة يتطابق مع إعدادات قناة المحتوى. يُرجى الرجوع إلى قسم سلوك تحديد المكان التلقائي للحصول على إرشادات حول قيم معيّنة لاستخدامها.

الاستماع إلى التغييرات على Spatializer

للاطّلاع على التغييرات في حالة Spatializer، يمكنك إضافة مستمع من خلال Spatializer.addOnSpatializerStateChangedListener(). وبالمثل، للاستماع إلى التغييرات المتعلّقة بمدى توفّر جهاز تتبُّع الرأس، يمكنك الاتصال بالرقم Spatializer.addOnHeadTrackerAvailableListener().

يمكن أن يكون ذلك مفيدًا إذا كنت تريد ضبط اختيار المقطع الصوتي أثناء التشغيل باستخدام معاودة اتصال المستمع. على سبيل المثال، عندما يربط مستخدم سماعة الرأس أو يفصلها عن الجهاز، تشير معاودة الاتصال onSpatializerAvailableChanged إلى ما إذا كان تأثير محدِّد الصوت المكاني متاحًا لتوجيه إخراج الصوت الجديد. في هذه المرحلة، يمكنك تعديل منطق اختيار المقطع الصوتي للّاعب ليتوافق مع الإمكانات الجديدة للجهاز. للحصول على تفاصيل حول سلوك اختيار المقاطع الصوتية في ExoPlayer، يُرجى الرجوع إلى قسم ExoPlayer والصوت المكاني.

ExoPlayer والصوت المكاني

إنّ الإصدارات الأخيرة من ExoPlayer تسهّل استخدام الصوت المكاني. إذا كنت تستخدم مكتبة ExoPlayer المستقلة (اسم الحزمة com.google.android.exoplayer2)، يضبط الإصدار 2.17 النظام الأساسي لإصدار صوت مكاني، في حين يقدّم الإصدار 2.18 قيودًا على عدد القنوات الصوتية. إذا كنت تستخدم وحدة ExoPlayer من مكتبة Media3 (اسم الحزمة androidx.media3)، ستتضمّن الإصدارات 1.0.0-beta01 والإصدارات الأحدث هذه التعديلات نفسها.

بعد تحديث اعتمادية ExoPlayer إلى أحدث إصدار، يحتاج تطبيقك فقط إلى تضمين محتوى يمكن تحديد مكانه.

قيود عدد القنوات الصوتية

عند استيفاء جميع الشروط الأربعة للصوت المكاني، يختار ExoPlayer مقطعًا صوتيًا متعدّد القنوات. إذا لم يكن كذلك، يختار ExoPlayer مسارًا استيريو بدلاً من ذلك. إذا تغيّرت سمات Spatializer، سيفعّل ExoPlayer مقطعًا صوتيًا جديدًا لاختيار مقطع صوتي يتطابق مع السمات الحالية. لاحظ أن اختيار المسار الجديد هذا قد يستغرق فترة قصيرة لإعادة التخزين المؤقت.

لإيقاف قيود عدد القنوات الصوتية، اضبط معلَمات اختيار المقطع الصوتي في المشغّل كما هو موضّح أدناه:

Kotlin

exoPlayer.trackSelectionParameters = DefaultTrackSelector.Parameters.Builder(context)
  .setConstrainAudioChannelCountToDeviceCapabilities(false)
  .build()

Java

exoPlayer.setTrackSelectionParameters(
  new DefaultTrackSelector.Parameters.Builder(context)
    .setConstrainAudioChannelCountToDeviceCapabilities(false)
    .build()
);

بالمثل، يمكنك تعديل معلَمات أداة اختيار المقاطع الصوتية الحالية لإيقاف قيود عدد القنوات الصوتية على النحو التالي:

Kotlin

val trackSelector = DefaultTrackSelector(context)
...
trackSelector.parameters = trackSelector.buildUponParameters()
  .setConstrainAudioChannelCountToDeviceCapabilities(false)
  .build()

Java

DefaultTrackSelector trackSelector = new DefaultTrackSelector(context);
...
trackSelector.setParameters(
  trackSelector
    .buildUponParameters()
    .setConstrainAudioChannelCountToDeviceCapabilities(false)
    .build()
);

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

اختيار المقطع الصوتي

عند إيقاف سلوك قيود عدد القنوات الصوتية في ExoPlayer، لا يختار ExoPlayer تلقائيًا مقطعًا صوتيًا يتطابق مع خصائص محدِّد الصوت المكاني في الجهاز. بدلاً من ذلك، يمكنك تخصيص منطق اختيار المسار في ExoPlayer من خلال ضبط معلمات اختيار المسار قبل التشغيل أو أثناءه. بشكل افتراضي، يختار ExoPlayer المسارات الصوتية المتماثلة مع المقطع الصوتي الأولي في ما يتعلق بنوع MIME (الترميز) وعدد القنوات ومعدل العينة.

تغيير معلمات تحديد المسار

لتغيير مَعلمات اختيار مسار الإحالة الناجحة في ExoPlayer، استخدِم Player.setTrackSelectionParameters(). وبالمثل، يمكنك الحصول على معلَمات ExoPlayer الحالية من خلال Player.getTrackSelectionParameters(). على سبيل المثال، لاختيار مقطع صوتي استيريو في منتصف التشغيل:

Kotlin

exoPlayer.trackSelectionParameters = exoPlayer.trackSelectionParameters
  .buildUpon()
  .setMaxAudioChannelCount(2)
  .build()

Java

exoPlayer.setTrackSelectionParameters(
  exoPlayer.getTrackSelectionParameters()
    .buildUpon()
    .setMaxAudioChannelCount(2)
    .build()
);

يُرجى العِلم أنّ تغيير معلَمات اختيار المقاطع الصوتية في منتصف التشغيل قد يؤدي إلى حدوث انقطاع في عملية التشغيل. يتوفّر مزيد من المعلومات حول ضبط مَعلمات اختيار مسار المشغّل في قسم اختيار المقطع الصوتي في مستندات ExoPlayer.

السلوك التلقائي لتحديد مكان البيانات

يتضمن سلوك تحديد المكان التلقائي في Android السلوكيات التالية التي يمكن أن يخصِّصها المصنّعون الأصليون للأجهزة:

  • يتم تحديد المكان المناسب لعرض المحتوى المتعدّد القنوات فقط وليس المحتوى المجسَّم. في حال عدم استخدام ExoPlayer، بناءً على تنسيق المحتوى الصوتي المتعدّد القنوات، قد تحتاج إلى ضبط الحد الأقصى لعدد القنوات التي يمكن إخراجها باستخدام برنامج فك ترميز الصوت إلى عدد كبير. ويضمن هذا أن يُخرج برنامج فك ترميز الصوت PCM متعدد القنوات حتى يتمكن النظام الأساسي من تحديد المكان.

    Kotlin

    val mediaFormat = MediaFormat()
    mediaFormat.setInteger(MediaFormat.KEY_MAX_OUTPUT_CHANNEL_COUNT, 99)
    

    Java

    MediaFormat mediaFormat = new MediaFormat();
    mediaFormat.setInteger(MediaFormat.KEY_MAX_OUTPUT_CHANNEL_COUNT, 99);
    

    للحصول على مثال عملي، يمكنك الاطّلاع على MediaCodecAudioRenderer.java من ExoPlayer. لإيقاف ميزة الصوت المكاني بنفسك، بغض النظر عن تخصيص المصنّع الأصلي للجهاز، راجع إيقاف الصوت المكاني.

  • AudioAttributes: يكون الصوت مؤهّلاً لاستخدام ميزة تحديد المكان إذا تم ضبط usage على USAGE_MEDIA أو USAGE_GAME.

  • AudioFormat: استخدِم قناع قناة يتضمّن على الأقل قنوات AudioFormat.CHANNEL_OUT_QUAD (الجانب الأمامي الأيسر والأيمن والخلفي والخلفي الأيمن) ليكون الصوت مؤهلاً لاستخدام ميزة تحديد المكان الصوتي. في المثال أدناه، نستخدم AudioFormat.CHANNEL_OUT_5POINT1 للمقطع الصوتي 5.1. استخدِم AudioFormat.CHANNEL_OUT_STEREO لإنشاء مقطع صوتي استيريو.

    إذا كنت تستخدم Media3، يمكنك استخدام Util.getAudioTrackChannelConfig(int channelCount) لتحويل عدد القنوات إلى قناع قناة.

    بالإضافة إلى ذلك، اضبط الترميز على AudioFormat.ENCODING_PCM_16BIT في حال ضبط برنامج فك الترميز لإخراج PCM متعدد القنوات.

    Kotlin

    val audioFormat = AudioFormat.Builder()
      .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
      .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
      .build()
    

    Java

    AudioFormat audioFormat = new AudioFormat.Builder()
      .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
      .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
      .build();
    

اختبار الصوت المكاني

تأكَّد من تفعيل ميزة الصوت المكاني على جهاز الاختبار:

  • بالنسبة إلى سمّاعات الرأس السلكية، انتقِل إلى إعدادات النظام > الصوت والاهتزاز > الصوت المكاني.
  • بالنسبة إلى سمّاعات الرأس اللاسلكية، انتقِل إلى إعدادات النظام > الأجهزة المتصلة > رمز الترس الخاص بجهازك اللاسلكي > الصوت المكاني.

للتحقّق من توفّر ميزة "الصوت المكاني" في التوجيه الحالي، عليك تشغيل الأمر adb shell dumpsys audio على جهازك. من المفترض أن تظهر المَعلمات التالية في الناتج عندما يكون التشغيل نشطًا:

Spatial audio:
mHasSpatializerEffect:true (effect present)
isSpatializerEnabled:true (routing dependent)