オーディオ機能

Android TV デバイスには、テレビのスピーカー、HDMI 接続のホームシアター、Bluetooth ヘッドフォンなど、複数のオーディオ出力を同時に接続できます。これらのオーディオ出力デバイスは、エンコード(Dolby Digital+、DTS、PCM)、サンプルレート、チャンネルなど、さまざまなオーディオ機能をサポートできます。たとえば、HDMI 接続のテレビはさまざまなエンコードをサポートしていますが、Bluetooth ヘッドフォンは通常 PCM のみをサポートしています。

使用可能なオーディオ デバイスのリストと転送先のオーディオ デバイスは、HDMI デバイスのホットプラグ、Bluetooth ヘッドフォンの接続または切断、ユーザーによるオーディオ設定の変更によっても変更される場合があります。音声出力機能は、アプリがメディアを再生している間でも変更される可能性があるため、アプリはこれらの変更に適切に対応し、ルーティングされた新しいオーディオ デバイスとその機能で再生を継続する必要があります。出力する音声形式が正しくないと、エラーが発生したり、音声が再生されなかったりすることがあります。

アプリは、同じコンテンツを複数のエンコードで出力し、オーディオ デバイスの機能に応じてユーザーに最適なオーディオ エクスペリエンスを提供できます。たとえば、テレビがドルビー デジタルをサポートしている場合はドルビー デジタルでエンコードされた音声ストリームが再生されますが、ドルビー デジタルがサポートされていない場合は、より広くサポートされている PCM 音声ストリームが選択されます。オーディオ ストリームを PCM に変換するために使用される組み込みの Android デコーダの一覧については、サポートされているメディア形式をご覧ください。

再生時に、ストリーミング アプリは、出力オーディオ デバイスでサポートされている最適な AudioFormat を使用して AudioTrack を作成する必要があります。

適切な形式のトラックを作成する

アプリは AudioTrack を作成して再生を開始し、getRoutedDevice() を呼び出して、音声を再生するデフォルトのオーディオ デバイスを決定する必要があります。たとえば、ルーティングされたデバイスとそのオーディオ機能を特定するためにのみ使用される、安全な短いサイレンス PCM エンコード トラックなどです。

サポートされているエンコードを取得する

デフォルトのオーディオ デバイスで使用可能なオーディオ形式を特定するには、getAudioProfiles()(API レベル 31 以降)または getEncodings()(API レベル 23 以降)を使用します。

サポートされているオーディオ プロファイルと形式を確認する

AudioProfile(API レベル 31 以上)または isDirectPlaybackSupported()(API レベル 29 以上)を使用して、サポートされている形式、チャンネル数、サンプルレートの組み合わせを確認します。

一部の Android デバイスは、出力オーディオ デバイスでサポートされているエンコード以外のエンコードをサポートできます。これらの追加形式は isDirectPlaybackSupported() で検出する必要があります。この場合、音声データは出力オーディオ デバイスでサポートされている形式に再エンコードされます。getEncodings() によって返されたリストに含まれていない場合でも、目的の形式のサポートを適切に確認するには、isDirectPlaybackSupported() を使用します。

予測オーディオ ルート

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() は、指定された AudioAttributes を使用して、現在ルーティングされているオーディオ デバイスでサポートされている AudioProfile オブジェクトのリストを返します。優先される AudioFormat アイテムのリストは、一致するサポートされている AudioProfile が見つかるまで反復処理されます。この AudioProfilebestAudioProfile として保存されます。最適なサンプルレートとチャンネル マスクは bestAudioProfile から決定されます。最後に、適切な AudioFormat インスタンスが作成されます。

音声トラックを作成する

アプリは、この情報を使用して、デフォルトのオーディオ デバイスでサポートされている(および選択したコンテンツで利用可能な)最高品質の AudioFormatAudioTrack を作成する必要があります。

オーディオ デバイスの変更をインターセプトする

音声デバイスの変更をインターセプトして対応するには、アプリは次のことを行う必要があります。

  • API レベルが 24 以上の場合は、OnRoutingChangedListener を追加して、オーディオ機器の変更(HDMI、Bluetooth など)をモニタリングします。
  • API レベル 23 では、AudioDeviceCallback を登録して、使用可能なオーディオ デバイスのリストに変更が加えられたことを受け取ります。
  • API レベル 21 と 22 では、HDMI プラグイベントをモニタリングし、ブロードキャストからの追加データを使用します。
  • また、AudioDeviceCallback はまだサポートされていないため、API 23 より前のデバイスの BluetoothDevice 状態の変化をモニタリングするために BroadcastReceiver を登録します。

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