Ses özellikleri

Android TV cihazlarının aynı anda bağlı birden fazla ses çıkışı olabilir: TV hoparlörleri, HDMI bağlantılı ev sineması, Bluetooth kulaklık vb. Bu ses çıkış cihazları; kodlamalar (Dolby Digital+, DTS ve PCM), örnek hızı ve kanallar gibi farklı ses özelliklerini destekleyebilir. Örneğin, HDMI bağlantılı TV'ler çok sayıda kodlamayı desteklerken bağlı Bluetooth kulaklıklar genellikle yalnızca PCM'yi destekler.

Kullanılabilir ses cihazlarının listesi ve yönlendirilen ses cihazı ayrıca, HDMI cihazlarının çalışırken takılması, Bluetooth kulaklığın bağlanması veya bağlantısının kesilmesi ya da kullanıcının ses ayarlarını değiştirmesi aracılığıyla da değişebilir. Uygulamalar medya oynatırken bile ses çıkışı özellikleri değişebileceğinden, uygulamaların bu değişikliklere sorunsuz uyum sağlaması ve yeni yönlendirilmiş ses cihazında ve yeteneklerinde çalmaya devam etmesi gerekir. Yanlış ses biçiminin çıkışı, hatalara veya ses çalmaya neden olabilir.

Uygulamalar, ses cihazı özelliklerine bağlı olarak kullanıcıya en iyi ses deneyimini sunmak için aynı içeriği birden fazla kodlamada verebilir. Örneğin, TV destekliyorsa Dolby Digital kodlamalı ses akışı oynatılır. Dolby Digital için destek verilmediğinde ise daha yaygın olarak desteklenen bir PCM ses akışı seçilir. Ses akışını PCM'ye dönüştürmek için kullanılan yerleşik Android kod çözücülerin listesini Desteklenen medya biçimleri bölümünde bulabilirsiniz.

Yayın uygulaması, oynatma sırasında çıkış ses cihazının desteklediği en iyi AudioFormat ile AudioTrack oluşturmalıdır.

Doğru biçime sahip parça oluşturma

Uygulamalar bir AudioTrack oluşturmalı, çalmaya başlamalı ve sesin çalınacağı varsayılan ses cihazını belirlemek için getRoutedDevice() numarasını aramalıdır. Bu, örneğin yalnızca yönlendirilen cihazı ve ses özelliklerini belirlemek için kullanılan güvenli, kısa sessiz bir PCM kodlamalı parça olabilir.

Desteklenen kodlamaları alın

Varsayılan ses cihazında kullanılabilen ses biçimlerini belirlemek için getAudioProfiles() (API düzeyi 31 ve sonraki sürümler) veya getEncodings() (API düzeyi 23 ve sonraki sürümler) kullanın.

Desteklenen ses profillerini ve biçimlerini kontrol etme

Desteklenen biçim, kanal sayısı ve örnek hızı kombinasyonlarını kontrol etmek için AudioProfile (API düzeyi 31 ve üzeri) veya isDirectPlaybackSupported() (API düzeyi 29 ve üzeri) kullanın.

Bazı Android cihazlar, çıkış ses cihazı tarafından desteklenenlerin ötesinde kodlamaları destekleyebilir. Bu ek biçimler isDirectPlaybackSupported() üzerinden algılanmalıdır. Bu durumlarda ses verileri, çıkış ses cihazının desteklediği bir biçime göre yeniden kodlanır. getEncodings() tarafından döndürülen listede bulunmasa bile istenen biçim için desteği doğru bir şekilde kontrol etmek üzere isDirectPlaybackSupported() işlevini kullanın.

Sesli rota beklentisi

Android 13 (API düzeyi 33) ise beklenti sesli rotalarını kullanıma sundu. Cihazın ses özelliği desteğini tahmin edebilir ve etkin ses cihazı için parçalar hazırlayabilirsiniz. Belirli bir biçim ve özellikler için şu anda yönlendirilmiş olan ses cihazında doğrudan oynatmanın desteklenip desteklenmediğini kontrol etmek için getDirectPlaybackSupport() aracını kullanabilirsiniz:

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
}

Alternatif olarak, yönlendirilmiş olan ses sistemi üzerinden doğrudan medya oynatma için hangi profillerin desteklendiğini sorgulayabilirsiniz. Desteklenmeyen veya örneğin, Android çerçevesi tarafından kodu dönüştürülecek olan profiller hariç tutulur:

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();
}

Bu örnekte preferredFormats, AudioFormat örneklerinden oluşan bir listedir. Listede en çok tercih edilen ilk sırada, en az tercih edilen son sırada olacak şekilde sıralanır. getDirectProfilesForAttributes(), sağlanan AudioAttributes ile o anda yönlendirilmiş ses cihazı için desteklenen AudioProfile nesnelerin listesini döndürür. Eşleşen bir AudioProfile öğe bulunana kadar tercih edilen AudioFormat öğelerin listesi yinelenir. Bu AudioProfile, bestAudioProfile olarak depolanıyor. En uygun örnek hızları ve kanal maskeleri, bestAudioProfile üzerinden belirlenir. Son olarak, uygun bir AudioFormat örneği oluşturulur.

Ses parçası oluştur

Uygulamalar, varsayılan ses cihazı tarafından desteklenen (ve seçili içerikte kullanılabilir) en yüksek kaliteli AudioFormat için bir AudioTrack oluşturmak üzere bu bilgileri kullanmalıdır.

Ses sistemi değişikliklerine müdahale edin

Ses sistemi değişikliklerine müdahale etmek ve tepki vermek için uygulamalar:

  • 24'e eşit veya 24'ten yüksek API seviyeleri için ses sistemi değişikliklerini (HDMI, Bluetooth vb.) izlemek üzere bir OnRoutingChangedListener ekleyin.
  • API düzeyi 23 için kullanılabilir ses cihazları listesindeki değişiklikleri almak üzere bir AudioDeviceCallback kaydedin.
  • API seviyeleri 21 ve 22 için HDMI prizi etkinliklerini izleyin ve yayınlardaki ekstra verileri kullanın.
  • Ayrıca AudioDeviceCallback henüz desteklenmediğinden API 23'ten önceki cihazlarda BluetoothDevice durum değişikliklerini izlemek için bir BroadcastReceiver kaydedin.

AudioTrack için ses sistemi değişikliği algılandığında uygulama, güncellenen ses özelliklerini kontrol etmeli ve gerekirse AudioTrack öğesini farklı bir AudioFormat ile yeniden oluşturmalıdır. Daha yüksek kaliteli bir kodlama artık destekleniyorsa veya önceden kullanılan kodlama artık desteklenmiyorsa bunu yapın.

Örnek kod

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);