Android TV は、テレビのスピーカー、HDMI ホームシアター、Bluetooth など、複数の同時音声出力をサポートしています。これらのデバイスは、さまざまなエンコード(Dolby Digital+、DTS、PCM など)、サンプルレート、チャンネルをサポートしています。HDMI 接続のテレビは多くのフォーマットをサポートしていますが、Bluetooth は PCM のみをサポートしています。
再生中に利用可能なオーディオ デバイスとルーティングが変更されることがあります。たとえば、HDMI のホットプラグ、Bluetooth の接続、設定の変更などです。アプリは、新しいデバイスの機能を使用して再生を継続できるように、適切に対応する必要があります。サポートされていないフォーマットを使用すると、エラーや無音状態が発生する可能性があります。
デバイスの機能に基づいて、最適なエクスペリエンスを実現するために複数のエンコードを提供します。 たとえば、Dolby Digital がサポートされている場合は Dolby Digital が使用され、サポートされていない場合は 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();
}
この例では、preferredFormats は AudioFormat インスタンスのリストです。
リストの先頭に最も優先度の高いものが、末尾に最も優先度の低いものが並んでいます。getDirectProfilesForAttributes() は、
指定された AudioAttributes を使用して、現在ルーティングされているオーディオ機器でサポートされている
AudioProfile オブジェクトのリストを返します。一致するサポート対象の AudioProfile が見つかるまで、優先される AudioFormat アイテムのリストが反復処理されます。これは
AudioProfile bestAudioProfile として保存されます。最適なサンプルレートとチャンネル マスクは bestAudioProfile から決定されます。最後に、適切な
AudioFormatインスタンスが作成されます。
音声トラックを作成する
アプリは、この情報を使用して、デフォルトのオーディオ機器でサポートされている(選択したコンテンツで利用可能な)最高品質の AudioFormat の AudioTrack を作成する必要があります。
オーディオ機器の変更をインターセプトする
オーディオ機器の変更をインターセプトして対応するには、アプリは次のことを行う必要があります。
- API レベル 24 以上の場合は、
OnRoutingChangedListenerを追加して、オーディオ機器の変更(HDMI、 Bluetooth など)をモニタリングします。 - API レベル 23 の場合は、
AudioDeviceCallbackを登録して、利用可能なオーディオ機器のリストの変更を受け取ります。 - API レベル 21 と 22 の場合は、HDMI プラグ イベントをモニタリングし、 ブロードキャストの追加データを使用します。
AudioDeviceCallbackは まだサポートされていないため、API 23 より前のデバイスではBroadcastReceiverを登録してBluetoothDeviceの状態 の変化をモニタリングします。
AudioTrack でオーディオ機器の変更が検出されたら、アプリは更新されたオーディオ機能を確認し、必要に応じて別の AudioFormat で AudioTrack を再作成する必要があります。より高品質のエンコードがサポートされるようになった場合や、以前使用していたエンコードがサポートされなくなった場合は、これを行います。
サンプルコード
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);