Các thiết bị Android TV có thể kết nối cùng lúc với nhiều đầu ra âm thanh: loa TV, hệ thống rạp chiếu phim tại nhà kết nối qua HDMI, tai nghe Bluetooth, v.v. Các thiết bị đầu ra âm thanh này có thể hỗ trợ nhiều chức năng âm thanh, chẳng hạn như mã hoá (Dolby Digital+, DTS và PCM), tốc độ lấy mẫu và số kênh. Ví dụ: TV kết nối qua HDMI hỗ trợ nhiều phương thức mã hoá, trong khi tai nghe Bluetooth thường chỉ hỗ trợ PCM.
Danh sách các thiết bị âm thanh hiện có và thiết bị âm thanh được định tuyến cũng có thể thay đổi bằng cách cắm nóng các thiết bị HDMI, kết nối hoặc ngắt kết nối tai nghe Bluetooth hoặc người dùng thay đổi chế độ cài đặt âm thanh. Vì khả năng đầu ra âm thanh có thể thay đổi ngay cả khi ứng dụng đang phát nội dung nghe nhìn, nên ứng dụng cần điều chỉnh một cách linh hoạt theo những thay đổi này và tiếp tục phát trên thiết bị âm thanh được định tuyến mới cũng như các chức năng của thiết bị đó. Việc xuất ra sai định dạng âm thanh có thể dẫn đến lỗi hoặc không phát được âm thanh.
Các ứng dụng có khả năng xuất cùng một nội dung ở nhiều định dạng mã hoá để mang đến cho người dùng trải nghiệm âm thanh tốt nhất, tuỳ thuộc vào khả năng của thiết bị âm thanh. Ví dụ: luồng âm thanh được mã hoá bằng Dolby Digital sẽ phát nếu TV hỗ trợ, trong khi luồng âm thanh PCM được hỗ trợ rộng rãi hơn sẽ được chọn khi không có hỗ trợ cho Dolby Digital. Bạn có thể xem danh sách các bộ giải mã tích hợp của Android dùng để chuyển đổi luồng âm thanh thành PCM trong phần Các định dạng nội dung nghe nhìn được hỗ trợ.
Vào thời điểm phát, ứng dụng phát trực tuyế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
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 để phát âm thanh.
Ví dụ: đây có thể là một bản nhạc PCM ngắn, an toàn và im lặng, chỉ dùng để xác định thiết bị được định tuyến và khả 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()
(API cấp 31 trở lên) hoặc getEncodings()
(API cấp 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
(API cấp 31 trở lên) hoặc isDirectPlaybackSupported()
(API cấp 29 trở lên) để kiểm tra các tổ hợp được hỗ trợ của đị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ợ định dạng mong muố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) đã giới thiệu 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 được định tuyến. Điều này không bao gồm mọi hồ sơ không được hỗ trợ hoặc sẽ được, chẳng hạn như đượ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à 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 đó lựa chọn được ưu tiên nhất sẽ đứng đầu danh sách và lựa chọn ít được ưu tiên nhất sẽ đứng cuối danh sách.
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 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 sử dụng thông tin này để tạo một AudioTrack
cho AudioFormat
có chất lượng cao nhất được 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 API cấp 24 trở lên, hãy thêm
OnRoutingChangedListener
để theo dõi các thay đổi về thiết bị âm thanh (HDMI, Bluetooth, v.v.). - Đối với API cấp 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 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.
- Đồng thời, hãy đăng ký một
BroadcastReceiver
để theo dõi các thay đổi về trạng tháiBluetoothDevice
cho các thiết bị có API thấp hơn 23, vìAudioDeviceCallback
chư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 đã 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 tại hệ thống hỗ trợ một phương thức mã hoá có 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);