Android TV cihazlara aynı anda birden fazla ses çıkışı bağlanabilir: TV hoparlörleri, HDMI ile bağlı ev sineması, 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ğlı TV'ler çok sayıda kodlamayı desteklerken Bluetooth kulaklıklar genellikle yalnızca PCM'yi destekler.
Kullanılabilir ses sistemlerinin listesi ve yönlendirilen ses sistemi, HDMI cihazların çalışırken 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ştirmesiyle 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 sisteminde ve bu sistemin özelliklerinde oynatmaya devam etmesi gerekir. Yanlış ses biçiminin çıkışı, hatalara veya sesin çalınmamasına neden olabilir.
Uygulamalar, ses sistemi özelliklerine bağlı olarak kullanıcıya en iyi ses deneyimini sunmak için aynı içeriği birden fazla kodlamada çıkışa aktarabilir. Örneğin, TV destekliyorsa Dolby Digital ile kodlanmış bir ses akışı oynatılır. Dolby Digital desteklenmiyorsa 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 sayfasında bulabilirsiniz.
Oynatma sırasında, yayın uygulaması çıkış ses sistemi tarafından desteklenen en iyi AudioFormat ile bir AudioTrack oluşturmalıdır.
Doğru biçimde parça oluşturma
Uygulamalar bir AudioTrack oluşturmalı, çalmaya başlamalı ve sesi çalacak varsayılan ses sistemini belirlemek için getRoutedDevice() işlevini çağırmalıdır.
Örneğin, bu, yalnızca yönlendirilen cihazı ve ses özelliklerini belirlemek için kullanılan güvenli, kısa ve sessiz bir PCM kodlu parça olabilir.
Desteklenen kodlamaları alma
Varsayılan ses sisteminde 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 örnekleme 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, çıkış ses sistemi tarafından desteklenenlerin ötesinde kodlamaları destekleyebilir. Bu ek biçimler isDirectPlaybackSupported() aracılığıyla algılanmalıdır. Bu durumlarda ses verileri, çıkış ses sistemi tarafından desteklenen bir biçimde yeniden kodlanır. isDirectPlaybackSupported() tarafından döndürülen listede olmasa bile istenen biçimin desteğini düzgün şekilde kontrol etmek için isDirectPlaybackSupported() kullanın.getEncodings()
Beklentiye dayalı sesli rota
Android 13 (API düzeyi 33) ile birlikte, ses rotalarını önceden tahmin etme özelliği kullanıma sunuldu. Cihaz ses özelliği desteğini tahmin edebilir ve etkin ses sistemi 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()
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 sistemi üzerinden doğrudan medya oynatma için hangi profillerin desteklendiğini sorgulayabilirsiniz. Bu, desteklenmeyen veya örneğin Android çerçevesi tarafından dönüştürülen profilleri kapsamaz:
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 örneklerinin listesidir. Liste, en çok tercih edilenden en az tercih edilene doğru sıralanır.
getDirectProfilesForAttributes() sağlanan AudioAttributes ile şu anda yönlendirilen ses sistemi için desteklenen AudioProfile nesnelerinin listesini döndürür. Tercih edilen AudioFormat öğelerinin listesi, eşleşen desteklenen bir AudioProfile bulunana kadar yinelenir. Bu AudioProfile, bestAudioProfile olarak saklanır.
Optimum örnekleme hızları ve kanal maskeleri bestAudioProfile'dan belirlenir.
Son olarak, uygun bir AudioFormat
örneği oluşturulur.
Ses parçası oluşturma
Uygulamalar, varsayılan ses sistemi tarafından desteklenen (ve seçilen içerikte kullanılabilen) AudioTrack en yüksek kalitede AudioFormat oluşturmak için bu bilgileri kullanmalıdır.
Ses sistemi değişikliklerini yakalama
Uygulamaların ses sistemi değişikliklerini yakalayıp bunlara tepki verebilmesi için:
- API düzeyi 24 veya daha yüksek olan uygulamalarda ses sistemi değişikliklerini (HDMI, Bluetooth vb.) izlemek için
OnRoutingChangedListenerekleyin. - API düzeyi 23 için, kullanılabilir ses sistemi listesindeki değişiklikleri almak üzere bir
AudioDeviceCallbackkaydedin. - API düzeyleri 21 ve 22 için HDMI takma etkinliklerini izleyin ve yayınlardaki ek verileri kullanın.
- Ayrıca,
BroadcastReceiverkaydederek API 23'ten eski cihazlardaBluetoothDevicedurum değişikliklerini izleyin. ÇünküAudioDeviceCallbackhenüz desteklenmemektedir.
AudioTrack için bir 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 kodlama artık destekleniyorsa veya daha önce kullanılan kodlama artık desteklenmiyorsa bu işlemi 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);