Android TV hỗ trợ nhiều đầu ra âm thanh đồng thời: loa TV, rạp chiếu phim tại nhà HDMI, Bluetooth, v.v. Các thiết bị này hỗ trợ nhiều phương thức mã hoá (như Dolby Digital+, DTS, PCM), tốc độ lấy mẫu và kênh. TV kết nối qua HDMI hỗ trợ nhiều định dạng, nhưng Bluetooth chỉ hỗ trợ PCM.
Các thiết bị âm thanh và định tuyến hiện có có thể thay đổi trong quá trình phát: người dùng có thể cắm nóng HDMI, kết nối Bluetooth hoặc sửa đổi chế độ cài đặt. Ứng dụng phải thích ứng linh hoạt để đảm bảo phát liên tục bằng các chức năng của thiết bị mới. Các định dạng không được hỗ trợ có thể gây ra lỗi hoặc không có âm thanh.
Cung cấp nhiều phương thức mã hoá để mang lại trải nghiệm tốt nhất dựa trên chức năng của thiết bị. Ví dụ: Dolby Digital được sử dụng nếu được hỗ trợ; nếu không, ứng dụng sẽ quay lại PCM. Xem Các định dạng nội dung nghe nhìn được hỗ trợ cho bộ giải mã Android chuyển đổi luồng thành PCM.
Tại thời điểm phát, ứng dụng phát trực tuyến của bạn sẽ tạo một AudioTrack có AudioFormat tốt nhất được thiết bị âm thanh đầu ra hỗ trợ.
Tạo bản nhạc có định dạng phù hợp
Ứng dụng phải tạo một AudioTrack, bắt đầu phát và gọi getRoutedDevice() để xác định thiết bị âm thanh mặc định phát âm thanh. Ví dụ: đây có thể là một bản nhạc được mã hoá PCM im lặng ngắn và an toàn, chỉ dùng để xác định thiết bị được định tuyến và chức năng âm thanh của thiết bị đó.
Lấy các phương thức mã hoá được hỗ trợ
Sử dụng getAudioProfiles() (cấp độ API 31 trở lên) hoặc getEncodings() (cấp độ API 23 trở lên) để xác định các định dạng âm thanh có trên thiết bị âm thanh mặc định.
Kiểm tra các cấu hình và định dạng âm thanh được hỗ trợ
Sử dụng AudioProfile (cấp độ API 31 trở lên) hoặc isDirectPlaybackSupported() (cấp độ API 29 trở lên) để kiểm tra các tổ hợp định dạng, số kênh và tốc độ lấy mẫu được hỗ trợ.
Một số thiết bị Android có thể hỗ trợ các phương thức mã hoá ngoài những phương thức mà thiết bị âm thanh đầu ra hỗ trợ. Các định dạng bổ sung này phải được phát hiện thông qua isDirectPlaybackSupported(). Trong những trường hợp này, dữ liệu âm thanh sẽ được mã hoá lại thành định dạng mà thiết bị âm thanh đầu ra hỗ trợ. Sử dụng isDirectPlaybackSupported() để kiểm tra đúng cách khả năng hỗ trợ cho định dạng đã chọn, ngay cả khi định dạng đó không có trong danh sách do getEncodings() trả về.
Tuyến âm thanh dự kiến
Android 13 (cấp độ API 33) đã ra mắt các tuyến âm thanh dự đoán. Bạn có thể dự đoán khả năng hỗ trợ thuộc tính âm thanh của thiết bị và chuẩn bị các bản âm thanh cho thiết bị âm thanh đang hoạt động. Bạn có thể sử dụng getDirectPlaybackSupport() để kiểm tra xem thiết bị âm thanh được định tuyến hiện tại có hỗ trợ tính năng phát trực tiếp cho một định dạng và thuộc tính nhất định hay không:
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
}
Ngoài ra, bạn có thể truy vấn xem cấu hình nào được hỗ trợ để phát nội dung nghe nhìn trực tiếp thông qua thiết bị âm thanh hiện được định tuyến. Điều này không bao gồm bất kỳ hồ sơ nào không được hỗ trợ hoặc chẳng hạn như sẽ được chuyển mã bằng khung Android:
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();
}
Trong ví dụ này, preferredFormats là danh sách các thực thể AudioFormat.
Danh sách này được sắp xếp theo thứ tự ưu tiên từ cao nhất đến thấp nhất. getDirectProfilesForAttributes() trả về danh sách các đối tượng AudioProfile được hỗ trợ cho thiết bị âm thanh hiện được định tuyến bằng AudioAttributes được cung cấp. Danh sách các mục AudioFormat ưu tiên được lặp lại cho đến khi tìm thấy AudioProfile được hỗ trợ phù hợp. AudioProfile này được lưu trữ dưới dạng bestAudioProfile. Tốc độ lấy mẫu và mặt nạ kênh tối ưu được xác định từ bestAudioProfile. Cuối cùng, một thực thể AudioFormat thích hợp sẽ được tạo.
Tạo bản âm thanh
Ứng dụng nên sử dụng thông tin này để tạo AudioTrack cho AudioFormat chất lượng cao nhất mà thiết bị âm thanh mặc định hỗ trợ (và có sẵn cho nội dung đã chọn).
Chặn các thay đổi về thiết bị âm thanh
Để chặn và phản ứng với các thay đổi về thiết bị âm thanh, ứng dụng nên:
- Đối với các cấp độ API bằng hoặc lớn hơn 24, hãy thêm một
OnRoutingChangedListenerđể theo dõi các thay đổi về thiết bị âm thanh (HDMI, Bluetooth, v.v.). - Đối với cấp độ API 23, hãy đăng ký
AudioDeviceCallbackđể nhận các thay đổi trong danh sách thiết bị âm thanh hiện có. - Đối với cấp độ API 21 và 22, hãy giám sát sự kiện cắm HDMI và sử dụng dữ liệu bổ sung từ các thông báo truyền tin.
- Đồng thời, hãy đăng ký
BroadcastReceiverđể theo dõi các thay đổi về trạng tháiBluetoothDevicecho các thiết bị thấp hơn API 23, vìAudioDeviceCallbackchưa được hỗ trợ.
Khi phát hiện thấy thay đổi về thiết bị âm thanh cho AudioTrack, ứng dụng sẽ kiểm tra các chức năng âm thanh đã cập nhật và nếu cần, hãy tạo lại AudioTrack bằng một AudioFormat khác. Hãy làm việc này nếu hiện đã hỗ trợ một phương thức mã hoá chất lượng cao hơn hoặc phương thức mã hoá đã dùng trước đó không còn được hỗ trợ nữa.
Mã mẫu
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);