音訊功能

Android TV 裝置可以同時連線多個音訊輸出裝置: 電視喇叭、連接 HDMI 的家庭電影院、藍牙耳機等。 這些音訊輸出裝置可以支援不同的音訊功能。 例如編碼 (Dolby Digital+、DTS 和 PCM)、取樣率和管道 舉例來說,可連接 HDMI 的電視可支援多種編碼方式 連線的藍牙耳機通常僅支援 PCM

此外,可用音訊裝置和轉送的音訊裝置清單也可能會變更 將 HDMI 裝置連接或拔除藍牙耳機 或使用者變更音訊設定由於音訊輸出功能 即使應用程式正在播放媒體也改變,應用程式還是要配合這些 ,並在新的轉送音訊裝置上繼續播放 即便沒有技術背景,也能因這些工具的功能而受益輸出的音訊格式有誤可能會導致錯誤或 未播放任何聲音。

應用程式能夠以多種編碼輸出相同的內容 視音訊裝置而定,為使用者提供最佳的音訊體驗 即便沒有技術背景,也能因這些工具的功能而受益舉例來說,系統會播放 Dolby Digital 編碼的音訊串流 (電視支援) 但支援更多廣泛的 PCM 音訊串流 而且沒有支援 Dolby Digital 時選擇內建 Android 清單 若想將音訊串流轉換為 PCM,請參閱 支援的媒體格式

在播放過程中,串流應用程式應建立 AudioTrack 擁有最好的 輸出內容支援的 AudioFormat 音訊裝置。

建立格式正確的音軌

應用程式應建立 AudioTrack、開始播放並呼叫 getRoutedDevice() ,決定要從哪個預設音訊裝置播放音效。 例如,這種安全的短靜音 PCM 編碼音軌,僅用於存取 判斷轉送裝置及其音訊功能。

取得支援的編碼

使用 getAudioProfiles()敬上 (API 級別 31 以上) 或 getEncodings() (API 級別 23 以上),以判斷 預設音訊裝置。

查看支援的音訊設定檔和格式

使用 AudioProfile (API 級別 31 以上) 或 isDirectPlaybackSupported() (API 級別 29 以上),檢查支援的格式組合, 頻道數量和取樣率

部分 Android 裝置可支援不支援的編碼 輸出音訊裝置。 透過「isDirectPlaybackSupported()」偵測到。在這類情況下 會重新編碼為輸出音訊裝置支援的格式。使用 isDirectPlaybackSupported():正確檢查是否支援所需格式 即使該元素不在 getEncodings() 傳回的清單中也一樣。

預測語音路徑

Android 13 (API 級別 33) 導入了預先設定的音訊路徑。你可以 預估裝置音訊屬性支援,並為啟用中的音軌做好準備 音訊裝置。別擔心!您可以使用 getDirectPlaybackSupport()敬上 檢查目前轉送的音訊是否支援直接播放功能 特定格式和屬性的裝置:

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
}

或者,您也可以查詢哪些設定檔可支援直接媒體 透過目前轉送的音訊裝置播放。排除任何設定檔 或某些網址可能是由 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();
}

在這個範例中,preferredFormatsAudioFormat 執行個體。訂購時間: 第一個是清單中哪個字詞優先 getDirectProfilesForAttributes()敬上 傳回支援的清單 AudioProfile 物件 目前轉送的音訊裝置 AudioAttributes。請注意, 優先處理偏好的 AudioFormat 項目,直到相符的支援項目為止 找到 AudioProfile。此 AudioProfile 儲存為 bestAudioProfile。 最佳取樣率和頻道遮罩的判定依據為 bestAudioProfile。 最後,適當的 AudioFormat 執行個體就會建立完成

建立音軌

應用程式應使用這項資訊建立AudioTrack 預設音訊裝置支援的最高畫質 AudioFormat (適用於所選內容)。

攔截音訊裝置變更

如要攔截及回應音訊裝置變更,應用程式應符合下列條件:

當偵測到 AudioTrack 的音訊裝置發生變化時,應用程式 應檢查更新後的音訊功能。如有需要,請重新執行 使用不同的 AudioFormatAudioTrack。如果高品質 編碼,或者先前使用的編碼為 。

程式碼範例

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