Kemampuan audio

Perangkat Android TV dapat memiliki beberapa output audio yang terhubung secara bersamaan: Speaker TV, bioskop rumah yang terhubung ke HDMI, headphone Bluetooth, dan sebagainya. Perangkat output audio ini dapat mendukung berbagai kemampuan audio, seperti pengkodean (Dolby Digital+, DTS, dan PCM), frekuensi sampel, dan saluran. Misalnya, TV yang terhubung ke HDMI memiliki dukungan untuk banyak encoding saat {i>headphone Bluetooth<i} yang tersambung biasanya hanya mendukung PCM.

Daftar perangkat audio yang tersedia dan perangkat audio yang dirutekan juga dapat berubah dengan perangkat HDMI {i>hot-plugging<i}, menghubungkan atau memutuskan {i>headphone Bluetooth<i}, atau pengguna mengubah pengaturan audio. Karena kemampuan output audio dapat berubah bahkan saat aplikasi memutar media, aplikasi harus beradaptasi dengan lancar berubah dan melanjutkan pemutaran di perangkat audio baru yang dirutekan dan kemampuan IT. Output dalam format audio yang salah dapat menyebabkan error atau tidak ada suara yang diputar.

Aplikasi memiliki kemampuan untuk menghasilkan konten yang sama dalam beberapa encoding untuk menawarkan pengalaman audio terbaik kepada pengguna, bergantung pada perangkat audio kemampuan IT. Misalnya, streaming audio yang dienkode dengan Dolby Digital diputar jika TV mendukungnya, sedangkan streaming audio PCM yang lebih luas dipilih saat tidak ada dukungan untuk Dolby Digital. Daftar fitur Android bawaan encoder yang digunakan untuk mengubah streaming audio menjadi PCM dapat ditemukan di Format media yang didukung.

Pada waktu pemutaran, aplikasi streaming harus membuat AudioTrack dengan yang terbaik AudioFormat didukung oleh output perangkat audio.

Buat lagu dengan format yang tepat

Aplikasi harus membuat AudioTrack, mulai memutarnya, dan memanggil getRoutedDevice() untuk menentukan perangkat audio default yang digunakan untuk memutar suara. Ini dapat berupa, misalnya, trek berenkode PCM senyap yang aman dan hanya digunakan untuk menentukan perangkat yang dirutekan dan kemampuan audionya.

Mendapatkan encoding yang didukung

Gunakan getAudioProfiles() (API level 31 dan yang lebih tinggi) atau getEncodings() (API level 23 dan yang lebih tinggi) untuk menentukan format audio yang tersedia di perangkat audio default.

Periksa profil dan format audio yang didukung

Gunakan AudioProfile (API level 31 dan yang lebih tinggi) atau isDirectPlaybackSupported() (API level 29 dan yang lebih tinggi) untuk memeriksa kombinasi format yang didukung, jumlah saluran, dan frekuensi sampel.

Beberapa perangkat Android mampu mendukung encoding di luar yang didukung oleh perangkat audio output. Format tambahan ini harus terdeteksi melalui isDirectPlaybackSupported(). Dalam kasus ini, data audio dienkode ulang ke format yang didukung oleh perangkat audio output. Gunakan isDirectPlaybackSupported() guna memeriksa dukungan untuk format yang diinginkan dengan benar meskipun tidak ada dalam daftar yang ditampilkan oleh getEncodings().

Rute audio antisipatif

Android 13 (API level 33) memperkenalkan rute audio antisipatif. Anda dapat mengantisipasi dukungan atribut audio perangkat dan menyiapkan jalur untuk perangkat audio. Anda dapat menggunakan getDirectPlaybackSupport() untuk memeriksa apakah pemutaran langsung didukung pada audio yang dirutekan saat ini perangkat untuk format dan atribut tertentu:

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
}

Atau, Anda dapat mengkueri profil mana yang didukung untuk media langsung memutar melalui perangkat audio yang saat ini dirutekan. Tidak mencakup profil apa pun yang tidak didukung atau, misalnya, di-transcoding oleh Android kerangka kerja:

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

Dalam contoh ini, preferredFormats adalah daftar Instance AudioFormat. Sudah dipesan dengan yang paling disukai terlebih dahulu, dan yang paling tidak disukai di akhir. getDirectProfilesForAttributes() akan menampilkan daftar respons Objek AudioProfile untuk perangkat audio yang saat ini dirutekan dengan AudioAttributes. Daftar item AudioFormat pilihan akan diiterasi hingga kecocokan didukung AudioProfile ditemukan. AudioProfile ini disimpan sebagai bestAudioProfile. Frekuensi sampel optimal dan mask saluran ditentukan dari bestAudioProfile. Terakhir, AudioFormat yang sesuai instance akan dibuat.

Buat trek audio

Aplikasi harus menggunakan informasi ini untuk membuat AudioTrack untuk AudioFormat berkualitas tertinggi yang didukung oleh perangkat audio default (dan tersedia untuk konten yang dipilih).

Mencegah perubahan perangkat audio

Untuk mencegat dan bereaksi terhadap perubahan perangkat audio, aplikasi harus:

  • Untuk level API yang sama dengan atau lebih besar dari 24, tambahkan elemen OnRoutingChangedListener untuk memantau perubahan perangkat audio (HDMI, Bluetooth, dan sebagainya).
  • Untuk level API 23, daftarkan AudioDeviceCallback untuk menerima perubahan dalam daftar perangkat audio yang tersedia.
  • Untuk level API 21 dan 22, pantau Peristiwa steker HDMI dan menggunakan data tambahan dari siaran.
  • Daftarkan juga BroadcastReceiver untuk memantau Perubahan status BluetoothDevice untuk perangkat yang lebih rendah dari API 23, karena AudioDeviceCallback tidak tetapi belum didukung.

Saat perubahan perangkat audio terdeteksi untuk AudioTrack, aplikasi tersebut harus memeriksa kemampuan audio yang telah diperbarui dan, jika perlu, membuat ulang AudioTrack dengan AudioFormat yang berbeda. Lakukan ini jika encoding saat ini didukung atau encoding yang sebelumnya digunakan tidak lagi didukung.

Kode contoh

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