Ses özellikleri

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ın BluetoothDevice durumundaki değişiklikleri izlemek için de BroadcastReceiver 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);