يمكن لأجهزة Android TV توصيل عدة إخراجات صوتية في الوقت نفسه على النحو التالي: مكبّرات صوت التلفزيون وسينما منزلية متصلة بكابل HDMI وسماعات رأس بلوتوث، وما إلى ذلك يمكن لأجهزة إخراج الصوت هذه أن تتوافق مع إمكانيات صوت مختلفة، مثل الترميزات (Dolby Digital+ وDTS وPCM) ومعدل العينة والقنوات. على سبيل المثال، تتوافق أجهزة التلفزيون المتصلة بكابل HDMI مع العديد من الترميزات. بينما تتوافق سماعات رأس Bluetooth المتصلة عادةً مع PCM فقط.
يمكن أيضًا أن تتغير قائمة الأجهزة السماعية المتاحة والجهاز الصوتي الموجّه من خلال التوصيل السريع لأجهزة HDMI أو توصيل سماعات رأس بلوتوث أو فصلها، أو عندما يغيّر المستخدم إعدادات الصوت. ونظرًا لأن إمكانات إخراج الصوت يمكن حتى عند تشغيل التطبيقات للوسائط، يجب أن تتكيّف التطبيقات مع هذه التغيير ومواصلة التشغيل على الجهاز السماعي الموجه الجديد والإمكانات. قد يؤدي إخراج تنسيق صوتي غير صحيح إلى حدوث أخطاء أو لا يتم تشغيل أي صوت.
بإمكان التطبيقات إنتاج المحتوى نفسه بترميزات متعددة. لمنح المستخدم أفضل تجربة صوتية استنادًا إلى الجهاز السماعي والإمكانات. على سبيل المثال، يتمّ تشغيل بث صوتي مرمّز من خلال Dolby Digital. إذا كان التلفزيون يتيح ذلك، في حين أنّ البث الصوتي بتنسيق PCM الذي يتم اعتماده على نطاق أوسع يتم اختياره في حالة عدم دعم Dolby Digital. قائمة أجهزة Android المدمَجة برامج فك الترميز المستخدمة لتحويل بث صوتي إلى PCM في تنسيقات الوسائط المسموح بها:
في وقت التشغيل، يجب أن ينشئ تطبيق البث
AudioTrack
مع الأفضل
يتوافق AudioFormat
مع الناتج
جهاز سماعي.
إنشاء مقطع صوتي بالتنسيق الصحيح
يجب أن تنشئ التطبيقات "AudioTrack
" وتبدأ تشغيله وتتصل
getRoutedDevice()
لتحديد جهاز الصوت الافتراضي الذي سيتم تشغيل الصوت منه.
ويمكن أن يكون ذلك مثلاً، مسار صوت مرمّز باستخدام PCM قصير آمن ويُستخدَم فقط
لتحديد الجهاز الذي يتم توجيهه وإمكانيات الصوت فيه.
الحصول على ترميزات متوافقة
استخدام
getAudioProfiles()
(المستوى 31 من واجهة برمجة التطبيقات والمستويات الأعلى)
getEncodings()
(المستوى 23 من واجهة برمجة التطبيقات والمستويات الأعلى) لتحديد تنسيقات الصوت المتوفّرة على
الجهاز السماعي التلقائي.
التحقّق من الملفات الشخصية والتنسيقات الصوتية المتوافقة
استخدام AudioProfile
(المستوى 31 من واجهة برمجة التطبيقات والمستويات الأعلى)
isDirectPlaybackSupported()
(المستوى 29 من واجهة برمجة التطبيقات والمستويات الأعلى) للاطّلاع على مجموعات التنسيقات المتوافقة
وعدد القنوات ومعدل العينة.
بإمكان بعض أجهزة Android إتاحة ترميزات غير تلك المتاحة
متوافق مع جهاز إخراج الصوت. يجب أن تكون هذه التنسيقات الإضافية
تم رصده من خلال isDirectPlaybackSupported()
. في هذه الحالات، تساعدنا البيانات الصوتية
تتم إعادة ترميزه إلى تنسيق متوافق مع جهاز إخراج الصوت. استخدام
isDirectPlaybackSupported()
للتحقق من توافق التنسيق المطلوب بشكل صحيح
حتى إذا لم يكن متوفّرًا في القائمة التي يعرضها getEncodings()
.
مسار الصوت الاستباقي
قدّم نظام التشغيل Android 13 (المستوى 33) في نظام التشغيل Android مسارات صوتية استباقية. يمكنك
استباق التوافق مع سمات الصوت للجهاز وإعداد المقاطع الصوتية
جهاز سماعي. يمكنك استخدام
getDirectPlaybackSupport()
للتحقّق مما إذا كان التشغيل المباشر متوافقًا مع الصوت الموجَّه حاليًا
الجهاز لتنسيق وسمات معيّنة:
Kotlin
val format = AudioFormat.Builder() .setEncoding(AudioFormat.ENCODING_E_AC3) .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) .setSampleRate(48000) .build() val attributes = AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .build() if (AudioManager.getDirectPlaybackSupport(format, attributes) != AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED ) { // The format and attributes are supported for direct playback // on the currently active routed audio path } else { // The format and attributes are NOT supported for direct playback // on the currently active routed audio path }
Java
AudioFormat format = new AudioFormat.Builder() .setEncoding(AudioFormat.ENCODING_E_AC3) .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) .setSampleRate(48000) .build(); AudioAttributes attributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .build(); if (AudioManager.getDirectPlaybackSupport(format, attributes) != AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED) { // The format and attributes are supported for direct playback // on the currently active routed audio path } else { // The format and attributes are NOT supported for direct playback // on the currently active routed audio path }
بدلاً من ذلك، يمكنك الاستعلام عن الملفات الشخصية المتاحة للوسائط المباشرة التشغيل عبر الجهاز الصوتي الموجّه حاليًا. باستثناء أي ملفات شخصية غير المتوافقة أو التي سيتم تحويل ترميزها مثلاً من خلال نظام التشغيل Android إطار العمل:
Kotlin
private fun findBestAudioFormat(audioAttributes: AudioAttributes): AudioFormat { val preferredFormats = listOf( AudioFormat.ENCODING_E_AC3, AudioFormat.ENCODING_AC3, AudioFormat.ENCODING_PCM_16BIT, AudioFormat.ENCODING_DEFAULT ) val audioProfiles = audioManager.getDirectProfilesForAttributes(audioAttributes) val bestAudioProfile = preferredFormats.firstNotNullOf { format -> audioProfiles.firstOrNull { it.format == format } } val sampleRate = findBestSampleRate(bestAudioProfile) val channelMask = findBestChannelMask(bestAudioProfile) return AudioFormat.Builder() .setEncoding(bestAudioProfile.format) .setSampleRate(sampleRate) .setChannelMask(channelMask) .build() }
Java
private AudioFormat findBestAudioFormat(AudioAttributes audioAttributes) { Stream<Integer> preferredFormats = Stream.<Integer>builder() .add(AudioFormat.ENCODING_E_AC3) .add(AudioFormat.ENCODING_AC3) .add(AudioFormat.ENCODING_PCM_16BIT) .add(AudioFormat.ENCODING_DEFAULT) .build(); Stream<AudioProfile> audioProfiles = audioManager.getDirectProfilesForAttributes(audioAttributes).stream(); AudioProfile bestAudioProfile = (AudioProfile) preferredFormats.map(format -> audioProfiles.filter(profile -> profile.getFormat() == format) .findFirst() .orElseThrow(NoSuchElementException::new) ); Integer sampleRate = findBestSampleRate(bestAudioProfile); Integer channelMask = findBestChannelMask(bestAudioProfile); return new AudioFormat.Builder() .setEncoding(bestAudioProfile.getFormat()) .setSampleRate(sampleRate) .setChannelMask(channelMask) .build(); }
في هذا المثال، preferredFormats
هي قائمة
AudioFormat
مثال مرتبة
مع الأكثر تفضيلاً أولاً في القائمة، والأقل تفضيلاً في النهاية.
getDirectProfilesForAttributes()
يؤدي إلى إرجاع قائمة
كائنات AudioProfile
الجهاز الصوتي الموجّه حاليًا مع
AudioAttributes
قائمة
يتم تكرار AudioFormat
من العناصر المفضلة حتى يتم دعم مطابقة
تم العثور على AudioProfile
. تم تخزين "AudioProfile
" هذا باسم bestAudioProfile
.
يتم تحديد أفضل معدلات العيّنات وأقنعة القنوات اعتبارًا من bestAudioProfile
.
أخيرًا، يمكن استخدام AudioFormat
إنشاء المثيل.
إنشاء مقطع صوتي
يجب أن تستخدم التطبيقات هذه المعلومات لإنشاء AudioTrack
AudioFormat
بأعلى جودة يتوافق مع الجهاز السماعي التلقائي
(ومتاحة للمحتوى المحدّد).
تغييرات اعتراض الصوت على الجهاز
لاعتراض التغييرات في الجهاز السماعي والاستجابة لها، يجب أن تنفّذ التطبيقات ما يلي:
- بالنسبة إلى مستويات واجهة برمجة التطبيقات التي تساوي أو تزيد عن 24، يمكنك إضافة
OnRoutingChangedListener
لمراقبة التغييرات في الجهاز السماعي (HDMI والبلوتوث وما إلى ذلك). - للمستوى 23 من واجهة برمجة التطبيقات، سجّل
AudioDeviceCallback
لتلقّي تغييرات في قائمة الأجهزة السماعية المتاحة. - بالنسبة إلى المستويَين 21 و22 من واجهة برمجة التطبيقات، يُرجى مراقبة ما يلي: أحداث قابس HDMI واستخدام البيانات الإضافية من عمليات البث.
- تسجيل
BroadcastReceiver
أيضًا لتتبُّعه تم تغيير حالةBluetoothDevice
. للأجهزة الأقل من واجهة برمجة التطبيقات 23، وذلك بسببAudioDeviceCallback
ليس حتى الآن.
عندما يتم رصد تغيير في الجهاز السماعي في "AudioTrack
"، يبدأ التطبيق
يجب التحقّق من إمكانات الصوت المعدّلة، وإذا لزم الأمر، إعادة إنشاء
AudioTrack
باستخدام AudioFormat
مختلف. افعل ذلك إذا كانت الجودة أعلى
أصبح الترميز متوفرًا الآن أو سيكون الترميز المستخدَم سابقًا
لم يعد متاحًا.
نموذج التعليمات البرمجية
Kotlin
// audioPlayer is a wrapper around an AudioTrack // which calls a callback for an AudioTrack write error audioPlayer.addAudioTrackWriteErrorListener { // error code can be checked here, // in case of write error try to recreate the audio track restartAudioTrack(findDefaultAudioDeviceInfo()) } audioPlayer.audioTrack.addOnRoutingChangedListener({ audioRouting -> audioRouting?.routedDevice?.let { audioDeviceInfo -> // use the updated audio routed device to determine // what audio format should be used if (needsAudioFormatChange(audioDeviceInfo)) { restartAudioTrack(audioDeviceInfo) } } }, handler)
Java
// audioPlayer is a wrapper around an AudioTrack // which calls a callback for an AudioTrack write error audioPlayer.addAudioTrackWriteErrorListener(new AudioTrackPlayer.AudioTrackWriteError() { @Override public void audioTrackWriteError(int errorCode) { // error code can be checked here, // in case of write error try to recreate the audio track restartAudioTrack(findDefaultAudioDeviceInfo()); } }); audioPlayer.getAudioTrack().addOnRoutingChangedListener(new AudioRouting.OnRoutingChangedListener() { @Override public void onRoutingChanged(AudioRouting audioRouting) { if (audioRouting != null && audioRouting.getRoutedDevice() != null) { AudioDeviceInfo audioDeviceInfo = audioRouting.getRoutedDevice(); // use the updated audio routed device to determine // what audio format should be used if (needsAudioFormatChange(audioDeviceInfo)) { restartAudioTrack(audioDeviceInfo); } } } }, handler);