Android TV hỗ trợ nhiều đầu ra âm thanh đồng thời: loa TV, hệ thống rạp chiếu phim tại nhà qua HDMI, Bluetooth và nhiều đầu ra khác. Những 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à tuyến đường 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. Các ứng dụng phải thích ứng một cách linh hoạt để đảm bảo quá trình 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 chế độ mã hoá để mang lại trải nghiệm tốt nhất dựa trên khả năng của thiết bị. Ví dụ: Dolby Digital sẽ được dùng nếu được hỗ trợ; nếu không, ứng dụng sẽ quay lại PCM. Hãy xem Các định dạng nội dung nghe nhìn được hỗ trợ cho các bộ giải mã Android chuyển đổi luồng thành PCM.
Khi 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 mà thiết bị âm thanh đầu ra hỗ trợ.
Tạo bản nhạc có định dạng phù hợp
Các ứng dụng nên 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 mà ứng dụng sẽ phát âm thanh. Ví dụ: đây có thể là một bản nhạc PCM mã hoá ngắn, an toàn và không có âm thanh, chỉ dùng để xác định thiết bị được định tuyến và các chức năng âm thanh của thiết bị đó.
Nhận các định dạng 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 định dạng và cấu hình â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 được hỗ trợ về định dạng, số kênh và tốc độ lấy mẫu.
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ợ. Bạn nên phát hiện các định dạng bổ sung này 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 một đị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ự đoán
Android 13 (API cấp 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 nhạc cho thiết bị âm thanh đang hoạt động. Bạn có thể dùng getDirectPlaybackSupport() để kiểm tra xem thiết bị âm thanh hiện được định tuyến có hỗ trợ chế độ 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 những cấu hình được hỗ trợ để phát trực tiếp nội dung nghe nhìn thông qua thiết bị âm thanh hiện đang được định tuyến. Điều này loại trừ mọi hồ sơ không được hỗ trợ hoặc, chẳng hạn như sẽ được khung Android chuyển mã:
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à một 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, trong đó ngôn ngữ được ưu tiên nhất sẽ xuất hiện đầu tiên và ngôn ngữ được ưu tiên ít nhất sẽ xuất hiện cuối cùng. 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 một 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 phiên bản AudioFormat thích hợp sẽ được tạo.
Tạo bản âm thanh
Các ứng dụng nên dùng thông tin này để tạo một AudioTrack cho AudioFormat có 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 cần:
- Đối với API cấp 24 trở lên, 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ý một
AudioDeviceCallbackđể nhận các thay đổi trong danh sách thiết bị âm thanh hiện có. - Đối với API cấp 21 và 22, hãy theo dõi các 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.
- Ngoài ra, hãy đăng ký một
BroadcastReceiverđể theo dõi các thay đổi về trạng tháiBluetoothDevicecho các thiết bị có API thấp hơn 23, vìAudioDeviceCallbackchưa được hỗ trợ.
Khi phát hiện thấy có thay đổi về thiết bị âm thanh đối với AudioTrack, ứng dụng sẽ kiểm tra các chức năng âm thanh mới 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 một phương thức mã hoá có chất lượng cao hơn hiện được hỗ trợ 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);