Android TV cihazları aynı anda bağlı birden fazla ses çıkışına sahip 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 sadece PCM'yi destekler.
Kullanılabilir ses cihazlarının ve yönlendirilen ses cihazlarının listesi, HDMI cihazları çalışır durumda takılarak, Bluetooth kulaklıkları bağlayarak veya bağlantısını keserek ya da kullanıcı ses ayarlarını değiştirerek de değişebilir. Ses çıkışı özellikleri uygulamalar medya oynatırken bile değişebileceğinden, uygulamaların bu değişikliklere uyum sağlamaları ve yeni yönlendirilen ses cihazında ve yeteneklerinde oynatmaya devam etmeleri gerekir. Yanlış ses biçiminde çıkış yapılması hatalara neden olabilir veya ses çalınmayabilir.
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 sunma yeteneğine sahiptir. Örneğin, TV destekliyorsa Dolby Digital kodlamalı bir ses akışı çalınırken Dolby Digital için destek olmadığında daha geniş çapta 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ülerinin listesini Desteklenen medya biçimleri bölümünde bulabilirsiniz.
Oynatma sırasında yayın uygulaması, çıkış ses cihazı tarafından desteklenen en iyi AudioFormat
özelliğine sahip bir AudioTrack
oluşturmalıdır.
Doğru biçimde bir 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ı çağırmalıdır.
Bu, örneğin, yalnızca yönlendirilen cihazı ve ses özelliklerini belirlemek için kullanılan güvenli ve kısa sessiz 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 üstü) veya getEncodings()
(API düzeyi 23 ve üstü) 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 üstü) veya isDirectPlaybackSupported()
(API düzeyi 29 ve üstü) kullanın.
Bazı Android cihazlar, ses çıkış cihazı tarafından desteklenenlerin ötesinde kodlamaları da destekleyebilir. Bu ek biçimler isDirectPlaybackSupported()
aracılığıyla algılanmalıdır. Böyle durumlarda ses verileri, ses çıkış cihazı tarafından desteklenen bir biçime göre yeniden kodlanır. İstenen biçim, getEncodings()
tarafından döndürülen listede yer almasa bile düzgün bir şekilde kontrol etmek için isDirectPlaybackSupported()
kullanın.
Beklenen ses rotası
Android 13 (API düzeyi 33), tahmini ses rotalarını kullanıma sunmuş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 halihazırda yönlendirilmiş olan ses cihazında doğrudan oynatmanın desteklenip desteklenmediğini kontrol etmek üzere 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önlendirilen ses cihazı aracılığıyla doğrudan medya oynatma için hangi profillerin desteklendiğini sorgulayabilirsiniz. Buna, desteklenmeyen veya örneğin Android çerçevesi tarafından kod dönüştürmesi yapılacak olan profiller dahil 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
örneklerinden oluşan bir listedir. En çok tercih edilenler listenin ilk sıralarında, en az tercih edilenler ise listenin sonunda yer alır.
getDirectProfilesForAttributes()
, sağlanan AudioAttributes
ile birlikte şu anda yönlendirilen ses cihazı için desteklenen AudioProfile
nesnelerinin bir listesini döndürür. Tercih edilen AudioFormat
öğe listesi, eşleşen bir desteklenen AudioProfile
bulunana kadar yinelenir. Bu AudioProfile
, bestAudioProfile
olarak depolanıyor.
En uygun örnek hızları ve kanal maskeleri bestAudioProfile
adresinden 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çilen içerikte kullanılabilen) en yüksek kalitedeki AudioFormat
için bir AudioTrack
oluşturmak üzere bu bilgileri kullanmalıdır.
Ses sistemi değişikliklerine müdahale et
Uygulamalar, ses sistemi değişikliklerine müdahale etmek ve tepki vermek için şunları yapmalıdır:
- 24'e eşit veya 24'ten yüksek API düzeyleri için ses cihazı değişikliklerini (HDMI, Bluetooth vb.) izlemek üzere bir
OnRoutingChangedListener
ekleyin. - API düzey 23 için kullanılabilir ses cihazı listesindeki değişiklikleri almak amacıyla bir
AudioDeviceCallback
kaydedin. - API düzeyi 21 ve 22 için HDMI fiş etkinliklerini izleyin ve yayınlardaki ekstra verileri kullanın.
AudioDeviceCallback
henüz desteklenmediğinden API 23'ten düşük cihazlarınBluetoothDevice
durumundaki değişiklikleri izlemek için deBroadcastReceiver
kaydedin.
AudioTrack
için ses cihazı değişikliği algılandığında, uygulama, güncellenmiş ses özelliklerini kontrol etmeli ve gerekirse AudioTrack
öğesini farklı bir AudioFormat
ile yeniden oluşturmalıdır. Bu işlemi, artık daha yüksek kaliteli bir kodlama destekleniyorsa veya önceden kullanılan kodlama artık desteklenmiyorsa 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);