Android TV cihazlarına aynı anda birden fazla ses çıkışı bağlanabilir: TV hoparlörleri, HDMI bağlantılı ev sinema sistemi, Bluetooth kulaklıklar vb. Bu ses çıkış cihazları; kodlamalar (Dolby Digital+, DTS ve PCM), örnekleme hızı ve kanallar gibi farklı ses özelliklerini destekleyebilir. Örneğin, HDMI ile bağlanan TV'ler çok sayıda kodlamayı desteklerken Bluetooth kulaklıklar genellikle yalnızca PCM'yi destekler.
Kullanılabilir ses cihazlarının listesi ve yönlendirilen ses cihazı, HDMI cihazların sıcak takılması, Bluetooth kulaklıkların bağlanması veya bağlantısının kesilmesi ya da kullanıcının ses ayarlarını değiştirmesi nedeniyle de değişebilir. Ses çıkışı özellikleri, uygulamalar medya oynatırken bile değişebileceğinden uygulamaların bu değişikliklere sorunsuz bir şekilde uyum sağlaması ve yeni yönlendirilen ses cihazında ve cihazın özelliklerinde oynatmaya devam etmesi gerekir. Yanlış ses biçimi yayınlamak hatalara veya sesin çalınmamasına neden olabilir.
Uygulamalar, ses cihazının özelliklerine bağlı olarak kullanıcıya en iyi ses deneyimini sunmak için aynı içeriği birden fazla kodlamada yayınlayabilir. Örneğin, TV desteklediği takdirde Dolby Digital kodlu bir ses akışı oynatılır. Dolby Digital desteği yoksa daha yaygın olarak desteklenen bir PCM ses akışı seçilir. Bir 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.
Oynatma sırasında akış uygulaması, çıkış ses cihazı tarafından desteklenen en iyi AudioFormat
ile bir AudioTrack
oluşturmalıdır.
Doğru biçime sahip bir parça oluşturma
Uygulamalar bir AudioTrack
oluşturup oynatmaya başlamalı ve sesin çalınacağı varsayılan ses cihazını belirlemek için getRoutedDevice()
işlevini çağırmalıdır.
Örneğin, yalnızca yönlendirilen cihazı ve ses özelliklerini belirlemek için kullanılan güvenli, kısa bir sessiz PCM kodlu parça olabilir.
Desteklenen kodlamaları alma
Varsayılan ses cihazında kullanılabilen ses biçimlerini belirlemek için getAudioProfiles()
(API düzeyi 31 ve üzeri) veya getEncodings()
(API düzeyi 23 ve üzeri) yöntemini kullanın.
Desteklenen ses profillerini ve biçimlerini kontrol etme
Desteklenen biçim, kanal sayısı ve örnekleme hızı kombinasyonlarını kontrol etmek için AudioProfile
(API düzeyi 31 ve üstü) veya isDirectPlaybackSupported()
(API düzeyi 29 ve üstü) işlevini kullanın.
Bazı Android cihazlar, çıkış ses cihazı tarafından desteklenenlerin dışındaki kodlamaları destekleyebilir. Bu ek biçimler isDirectPlaybackSupported()
aracılığıyla algılanmalıdır. Bu durumlarda ses verileri, çıkış ses cihazı tarafından desteklenen bir biçime yeniden kodlanır. getEncodings()
tarafından döndürülen listede yer almıyor olsa bile, istenen biçimin desteğini doğru şekilde kontrol etmek için isDirectPlaybackSupported()
işlevini kullanın.
Tahmini ses rotası
Android 13 (API düzeyi 33) ile birlikte tahmini ses yolları kullanıma sunulmuştur. Cihaz 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önlendirilen ses cihazında doğrudan oynatmanın desteklenip desteklenmediğini kontrol etmek üzere getDirectPlaybackSupport()
simgesini 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, şu anda yönlendirilen ses cihazı üzerinden doğrudan medya oynatma için hangi profillerin desteklendiğini sorgulayabilirsiniz. Desteklenmeyen veya örneğin Android çerçevesi tarafından kod dönüştürülecek tüm profiller bu kapsamda değildir:
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
örneklerini içeren bir listetir. En çok tercih edilenler listede ilk, en az tercih edilenler ise en son sırada olacak şekilde sıralanır.
getDirectProfilesForAttributes()
, sağlanan AudioAttributes
ile birlikte, şu anda yönlendirilen ses cihazı için desteklenen AudioProfile
nesnelerinin listesini döndürür. Tercih edilen AudioFormat
öğelerinin listesi, eşleşen bir desteklenen AudioProfile
bulunana kadar iterasyonla incelenir. Bu AudioProfile
, bestAudioProfile
olarak saklanır.
Optimum örnekleme hızları ve kanal maskeleri bestAudioProfile
'ten belirlenir.
Son olarak, uygun bir AudioFormat
örneği oluşturulur.
Ses parçası oluşturma
Uygulamalar, varsayılan ses cihazı tarafından desteklenen (ve seçilen içerik için kullanılabilen) en yüksek kaliteli AudioFormat
için bir AudioTrack
oluşturmak üzere bu bilgileri kullanmalıdır.
Ses cihazı değişikliklerini engelleme
Ses cihazı değişikliklerini engellemek ve bunlara tepki vermek için uygulamaların:
- 24'e eşit veya 24'ten yüksek API seviyeleri için ses cihazı değişikliklerini (HDMI, Bluetooth vb.) izlemek üzere bir
OnRoutingChangedListener
ekleyin. - API düzeyi 23 için, kullanılabilir ses cihazı listesinde yapılan değişiklikleri almak üzere bir
AudioDeviceCallback
kaydedin. - 21 ve 22 API düzeyleri için HDMI fişi etkinliği olup olmadığını izleyin ve yayınlardan elde edilen ek verileri kullanın.
AudioDeviceCallback
henüz desteklenmediğinden, API 23'ten eski sürümlerinBluetoothDevice
durum değişikliklerini izlemek için birBroadcastReceiver
de kaydedin.
AudioTrack
için ses cihazı değişikliği algılandığında uygulama, güncellenen ses özelliklerini kontrol etmeli ve gerekirse AudioTrack
'yi farklı bir AudioFormat
ile yeniden oluşturmalıdır. Daha yüksek kaliteli bir kodlama artık destekliyorsa veya daha önce 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);