يمكن أن تتضمّن أجهزة Android TV العديد من مخارج الصوت المتصلة في الوقت نفسه، مثل مكبّرات صوت التلفزيون ونظام السينما المنزلية المتصل عبر HDMI وسماعات الرأس التي تتضمّن بلوتوث وغير ذلك. يمكن أن تتوافق أجهزة إخراج الصوت هذه مع إمكانات صوتية مختلفة، مثل عمليات الترميز (Dolby Digital+ وDTS وPCM) ومعدّل أخذ العينات والقنوات. على سبيل المثال، تتوافق أجهزة التلفزيون المتصلة عبر HDMI مع العديد من عمليات الترميز، بينما تتوافق سماعات الرأس البلوتوث المتصلة عادةً مع 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()
.
مسار صوتي استباقي
قدّم الإصدار 13 من نظام التشغيل Android (المستوى 33 لواجهة برمجة التطبيقات) ميزة "توقُّع مسارات الصوت". يمكنك توقّع إتاحة سمات الصوت على الجهاز وإعداد المقاطع الصوتية لجهاز الصوت النشط. يمكنك استخدام
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);