오디오 기능

Android TV 기기는 여러 개의 오디오 출력을 동시에 연결할 수 있습니다. TV 스피커, HDMI 커넥티드 홈 시네마, 블루투스 헤드폰 등 이러한 오디오 출력 장치는 다양한 오디오 기능을 지원할 수 있습니다. 인코딩 (Dolby Digital+, DTS 및 PCM), 샘플링 레이트 및 채널 등입니다. 예를 들어 HDMI로 연결된 TV는 다양한 인코딩을 지원합니다. 연결된 블루투스 헤드폰은 일반적으로 PCM만 지원합니다.

사용 가능한 오디오 기기 및 라우팅된 오디오 기기 목록도 변경될 수 있습니다. HDMI 기기를 핫플러그하거나 블루투스 헤드폰을 연결하거나 연결 해제하거나 사용자가 오디오 설정을 변경하는 것입니다. 오디오 출력 기능은 미디어가 재생될 때에도 적절하게 적응할 수 있어야 합니다. 변경하고 계속 재생할 수 있으며 기능을 제공합니다 잘못된 오디오 형식을 출력하면 오류가 발생하거나 소리가 나지 않습니다.

앱은 동일한 콘텐츠를 여러 인코딩으로 출력할 수 있음 최적의 오디오 환경을 제공하기 위해 기능을 제공합니다 예를 들어 Dolby Digital로 인코딩된 오디오 스트림이 더 광범위하게 지원되는 PCM 오디오 스트림은 Dolby Digital이 지원되지 않을 때 선택됩니다. 내장된 Android 기기 목록 오디오 스트림을 PCM으로 변환하는 데 사용되는 디코더는 지원되는 미디어 형식

스트리밍 앱은 재생 시 최고의 가격의 AudioTrack 출력에서 지원되는 AudioFormat 오디오 기기입니다.

올바른 형식으로 트랙 만들기

앱에서 AudioTrack를 만들어 재생을 시작하고 getRoutedDevice() 를 사용하여 소리를 재생할 기본 오디오 기기를 결정합니다. 이것은 예를 들어, 잡음이 클 수 없으며 라우팅된 장치와 오디오 기능을 결정합니다.

지원되는 인코딩 가져오기

사용 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
}

자바

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

자바

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

이 예에서 preferredFormats는 인스턴스 AudioFormat개 순서가 지정됨 가장 선호되는 순서대로 나열되며 가장 낮은 순위를 차지할 수 있습니다. getDirectProfilesForAttributes() 드림 지원되는 목록의 AudioProfile 객체를 현재 라우팅된 오디오 기기를 AudioAttributes: 목록의 선호하는 AudioFormat 항목은 일치하는 항목이 지원될 때까지 반복됩니다. AudioProfile을(를) 찾았습니다. 이 AudioProfilebestAudioProfile로 저장됩니다. 최적의 샘플링 레이트와 채널 마스크는 bestAudioProfile에서 결정됩니다. 마지막으로 적절한 AudioFormat 인스턴스를 만듭니다

오디오 트랙 만들기

앱은 이 정보를 사용하여AudioTrack 기본 오디오 기기에서 지원하는 최고 품질의 AudioFormat (선택한 콘텐츠에서만 사용 가능)

오디오 기기 변경사항 가로채기

오디오 기기 변경사항을 가로채서 반응하려면 앱은 다음을 실행해야 합니다.

  • API 수준이 24 이상인 경우 OnRoutingChangedListener 드림 을 사용하여 오디오 기기 변경사항 (HDMI, 블루투스 등)을 모니터링할 수 있습니다.
  • API 수준 23의 경우 AudioDeviceCallback 드림 사용 가능한 오디오 기기 목록의 변경사항을 수신합니다.
  • API 수준 21 및 22의 경우 HDMI 플러그 이벤트 브로드캐스트의 추가 데이터를 사용합니다.
  • 모니터링할 BroadcastReceiver도 등록 BluetoothDevice 상태 변경 API 23보다 낮은 기기의 경우 AudioDeviceCallback이(가) 아닌 경우 아직 지원되지 않습니다.

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