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

يمكن لأجهزة 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);