mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Audio passthrough: handle unset audio format channel count
With HLS chunkless preparation, audio formats may have no value for channel count. In this case, the DefaultAudioSink will either query the platform for a supported channel count (API 29+) or assume a max channel count based on the encoding spec in order to decide whether the audio format can be played with audio passthrough. Issue: google/ExoPlayer#10204 #minor-release PiperOrigin-RevId: 453644548 (cherry picked from commit 86973382335156abaa76770c6897d28460fdde36)
This commit is contained in:
parent
f5dc99f596
commit
31c7ccbc49
@ -45,6 +45,10 @@
|
|||||||
* Change the return type of `AudioAttributes.getAudioAttributesV21()` from
|
* Change the return type of `AudioAttributes.getAudioAttributesV21()` from
|
||||||
`android.media.AudioAttributes` to a new `AudioAttributesV21` wrapper
|
`android.media.AudioAttributes` to a new `AudioAttributesV21` wrapper
|
||||||
class, to prevent slow ART verification on API < 21.
|
class, to prevent slow ART verification on API < 21.
|
||||||
|
* Query the platform (API 29+) or assume the audio encoding channel count
|
||||||
|
for audio passthrough when the format audio channel count is unset,
|
||||||
|
which occurs with HLS chunkless preparation
|
||||||
|
([10204](https://github.com/google/ExoPlayer/issues/10204)).
|
||||||
* Ad playback / IMA:
|
* Ad playback / IMA:
|
||||||
* Decrease ad polling rate from every 100ms to every 200ms, to line up
|
* Decrease ad polling rate from every 100ms to every 200ms, to line up
|
||||||
with Media Rating Council (MRC) recommendations.
|
with Media Rating Council (MRC) recommendations.
|
||||||
|
@ -15,22 +15,29 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer.audio;
|
package androidx.media3.exoplayer.audio;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
|
import android.media.AudioAttributes;
|
||||||
import android.media.AudioFormat;
|
import android.media.AudioFormat;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.media.AudioTrack;
|
import android.media.AudioTrack;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.provider.Settings.Global;
|
import android.provider.Settings.Global;
|
||||||
|
import android.util.Pair;
|
||||||
import androidx.annotation.DoNotInline;
|
import androidx.annotation.DoNotInline;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.Format;
|
||||||
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@ -54,18 +61,20 @@ public final class AudioCapabilities {
|
|||||||
},
|
},
|
||||||
DEFAULT_MAX_CHANNEL_COUNT);
|
DEFAULT_MAX_CHANNEL_COUNT);
|
||||||
|
|
||||||
/** Array of all surround sound encodings that a device may be capable of playing. */
|
/**
|
||||||
@SuppressWarnings("InlinedApi")
|
* All surround sound encodings that a device may be capable of playing mapped to a maximum
|
||||||
private static final int[] ALL_SURROUND_ENCODINGS =
|
* channel count.
|
||||||
new int[] {
|
*/
|
||||||
AudioFormat.ENCODING_AC3,
|
private static final ImmutableMap<Integer, Integer> ALL_SURROUND_ENCODINGS_AND_MAX_CHANNELS =
|
||||||
AudioFormat.ENCODING_E_AC3,
|
new ImmutableMap.Builder<Integer, Integer>()
|
||||||
AudioFormat.ENCODING_E_AC3_JOC,
|
.put(C.ENCODING_AC3, 6)
|
||||||
AudioFormat.ENCODING_AC4,
|
.put(C.ENCODING_AC4, 6)
|
||||||
AudioFormat.ENCODING_DOLBY_TRUEHD,
|
.put(C.ENCODING_DTS, 6)
|
||||||
AudioFormat.ENCODING_DTS,
|
.put(C.ENCODING_E_AC3_JOC, 6)
|
||||||
AudioFormat.ENCODING_DTS_HD,
|
.put(C.ENCODING_E_AC3, 8)
|
||||||
};
|
.put(C.ENCODING_DTS_HD, 8)
|
||||||
|
.put(C.ENCODING_DOLBY_TRUEHD, 8)
|
||||||
|
.buildOrThrow();
|
||||||
|
|
||||||
/** Global settings key for devices that can specify external surround sound. */
|
/** Global settings key for devices that can specify external surround sound. */
|
||||||
private static final String EXTERNAL_SURROUND_SOUND_KEY = "external_surround_sound_enabled";
|
private static final String EXTERNAL_SURROUND_SOUND_KEY = "external_surround_sound_enabled";
|
||||||
@ -158,6 +167,62 @@ public final class AudioCapabilities {
|
|||||||
return maxChannelCount;
|
return maxChannelCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns whether the device can do passthrough playback for {@code format}. */
|
||||||
|
public boolean isPassthroughPlaybackSupported(Format format) {
|
||||||
|
return getEncodingAndChannelConfigForPassthrough(format) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the encoding and channel config to use when configuring an {@link AudioTrack} in
|
||||||
|
* passthrough mode for the specified {@link Format}. Returns {@code null} if passthrough of the
|
||||||
|
* format is unsupported.
|
||||||
|
*
|
||||||
|
* @param format The {@link Format}.
|
||||||
|
* @return The encoding and channel config to use, or {@code null} if passthrough of the format is
|
||||||
|
* unsupported.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public Pair<Integer, Integer> getEncodingAndChannelConfigForPassthrough(Format format) {
|
||||||
|
@C.Encoding
|
||||||
|
int encoding = MimeTypes.getEncoding(checkNotNull(format.sampleMimeType), format.codecs);
|
||||||
|
// Check that this is an encoding known to work for passthrough. This avoids trying to use
|
||||||
|
// passthrough with an encoding where the device/app reports it's capable but it is untested or
|
||||||
|
// known to be broken (for example AAC-LC).
|
||||||
|
if (!ALL_SURROUND_ENCODINGS_AND_MAX_CHANNELS.containsKey(encoding)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encoding == C.ENCODING_E_AC3_JOC && !supportsEncoding(C.ENCODING_E_AC3_JOC)) {
|
||||||
|
// E-AC3 receivers support E-AC3 JOC streams (but decode only the base layer).
|
||||||
|
encoding = C.ENCODING_E_AC3;
|
||||||
|
} else if (encoding == C.ENCODING_DTS_HD && !supportsEncoding(C.ENCODING_DTS_HD)) {
|
||||||
|
// DTS receivers support DTS-HD streams (but decode only the core layer).
|
||||||
|
encoding = C.ENCODING_DTS;
|
||||||
|
}
|
||||||
|
if (!supportsEncoding(encoding)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int channelCount;
|
||||||
|
if (format.channelCount == Format.NO_VALUE || encoding == C.ENCODING_E_AC3_JOC) {
|
||||||
|
// In HLS chunkless preparation, the format channel count and sample rate may be unset. See
|
||||||
|
// https://github.com/google/ExoPlayer/issues/10204 and b/222127949 for more details.
|
||||||
|
// For E-AC3 JOC, the format is object based so the format channel count is arbitrary.
|
||||||
|
int sampleRate =
|
||||||
|
format.sampleRate != Format.NO_VALUE ? format.sampleRate : DEFAULT_SAMPLE_RATE_HZ;
|
||||||
|
channelCount = getMaxSupportedChannelCountForPassthrough(encoding, sampleRate);
|
||||||
|
} else {
|
||||||
|
channelCount = format.channelCount;
|
||||||
|
if (channelCount > maxChannelCount) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int channelConfig = getChannelConfigForPassthrough(channelCount);
|
||||||
|
if (channelConfig == AudioFormat.CHANNEL_INVALID) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Pair.create(encoding, channelConfig);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(@Nullable Object other) {
|
public boolean equals(@Nullable Object other) {
|
||||||
if (this == other) {
|
if (this == other) {
|
||||||
@ -190,28 +255,93 @@ public final class AudioCapabilities {
|
|||||||
&& ("Amazon".equals(Util.MANUFACTURER) || "Xiaomi".equals(Util.MANUFACTURER));
|
&& ("Amazon".equals(Util.MANUFACTURER) || "Xiaomi".equals(Util.MANUFACTURER));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum number of channels supported for passthrough playback of audio in the given
|
||||||
|
* encoding, or {@code 0} if the format is unsupported.
|
||||||
|
*/
|
||||||
|
private static int getMaxSupportedChannelCountForPassthrough(
|
||||||
|
@C.Encoding int encoding, int sampleRate) {
|
||||||
|
// From API 29 we can get the channel count from the platform, but before then there is no way
|
||||||
|
// to query the platform so we assume the channel count matches the maximum channel count per
|
||||||
|
// audio encoding spec.
|
||||||
|
if (Util.SDK_INT >= 29) {
|
||||||
|
return Api29.getMaxSupportedChannelCountForPassthrough(encoding, sampleRate);
|
||||||
|
}
|
||||||
|
return checkNotNull(ALL_SURROUND_ENCODINGS_AND_MAX_CHANNELS.getOrDefault(encoding, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getChannelConfigForPassthrough(int channelCount) {
|
||||||
|
if (Util.SDK_INT <= 28) {
|
||||||
|
// In passthrough mode the channel count used to configure the audio track doesn't affect how
|
||||||
|
// the stream is handled, except that some devices do overly-strict channel configuration
|
||||||
|
// checks. Therefore we override the channel count so that a known-working channel
|
||||||
|
// configuration is chosen in all cases. See [Internal: b/29116190].
|
||||||
|
if (channelCount == 7) {
|
||||||
|
channelCount = 8;
|
||||||
|
} else if (channelCount == 3 || channelCount == 4 || channelCount == 5) {
|
||||||
|
channelCount = 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Workaround for Nexus Player not reporting support for mono passthrough. See
|
||||||
|
// [Internal: b/34268671].
|
||||||
|
if (Util.SDK_INT <= 26 && "fugu".equals(Util.DEVICE) && channelCount == 1) {
|
||||||
|
channelCount = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Util.getAudioTrackChannelConfig(channelCount);
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresApi(29)
|
@RequiresApi(29)
|
||||||
private static final class Api29 {
|
private static final class Api29 {
|
||||||
|
private static final AudioAttributes DEFAULT_AUDIO_ATTRIBUTES =
|
||||||
|
new AudioAttributes.Builder()
|
||||||
|
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||||
|
.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
|
||||||
|
.setFlags(0)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private Api29() {}
|
||||||
|
|
||||||
@DoNotInline
|
@DoNotInline
|
||||||
public static int[] getDirectPlaybackSupportedEncodings() {
|
public static int[] getDirectPlaybackSupportedEncodings() {
|
||||||
ImmutableList.Builder<Integer> supportedEncodingsListBuilder = ImmutableList.builder();
|
ImmutableList.Builder<Integer> supportedEncodingsListBuilder = ImmutableList.builder();
|
||||||
for (int encoding : ALL_SURROUND_ENCODINGS) {
|
for (int encoding : ALL_SURROUND_ENCODINGS_AND_MAX_CHANNELS.keySet()) {
|
||||||
if (AudioTrack.isDirectPlaybackSupported(
|
if (AudioTrack.isDirectPlaybackSupported(
|
||||||
new AudioFormat.Builder()
|
new AudioFormat.Builder()
|
||||||
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
|
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
|
||||||
.setEncoding(encoding)
|
.setEncoding(encoding)
|
||||||
.setSampleRate(DEFAULT_SAMPLE_RATE_HZ)
|
.setSampleRate(DEFAULT_SAMPLE_RATE_HZ)
|
||||||
.build(),
|
.build(),
|
||||||
new android.media.AudioAttributes.Builder()
|
DEFAULT_AUDIO_ATTRIBUTES)) {
|
||||||
.setUsage(android.media.AudioAttributes.USAGE_MEDIA)
|
|
||||||
.setContentType(android.media.AudioAttributes.CONTENT_TYPE_MOVIE)
|
|
||||||
.setFlags(0)
|
|
||||||
.build())) {
|
|
||||||
supportedEncodingsListBuilder.add(encoding);
|
supportedEncodingsListBuilder.add(encoding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
supportedEncodingsListBuilder.add(AudioFormat.ENCODING_PCM_16BIT);
|
supportedEncodingsListBuilder.add(AudioFormat.ENCODING_PCM_16BIT);
|
||||||
return Ints.toArray(supportedEncodingsListBuilder.build());
|
return Ints.toArray(supportedEncodingsListBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum number of channels supported for passthrough playback of audio in the
|
||||||
|
* given format, or {@code 0} if the format is unsupported.
|
||||||
|
*/
|
||||||
|
@DoNotInline
|
||||||
|
public static int getMaxSupportedChannelCountForPassthrough(
|
||||||
|
@C.Encoding int encoding, int sampleRate) {
|
||||||
|
// TODO(internal b/234351617): Query supported channel masks directly once it's supported,
|
||||||
|
// see also b/25994457.
|
||||||
|
for (int channelCount = DEFAULT_MAX_CHANNEL_COUNT; channelCount > 0; channelCount--) {
|
||||||
|
AudioFormat audioFormat =
|
||||||
|
new AudioFormat.Builder()
|
||||||
|
.setEncoding(encoding)
|
||||||
|
.setSampleRate(sampleRate)
|
||||||
|
.setChannelMask(Util.getAudioTrackChannelConfig(channelCount))
|
||||||
|
.build();
|
||||||
|
if (AudioTrack.isDirectPlaybackSupported(audioFormat, DEFAULT_AUDIO_ATTRIBUTES)) {
|
||||||
|
return channelCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -684,7 +684,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
if (!offloadDisabledUntilNextConfiguration && useOffloadedPlayback(format, audioAttributes)) {
|
if (!offloadDisabledUntilNextConfiguration && useOffloadedPlayback(format, audioAttributes)) {
|
||||||
return SINK_FORMAT_SUPPORTED_DIRECTLY;
|
return SINK_FORMAT_SUPPORTED_DIRECTLY;
|
||||||
}
|
}
|
||||||
if (isPassthroughPlaybackSupported(format, audioCapabilities)) {
|
if (audioCapabilities.isPassthroughPlaybackSupported(format)) {
|
||||||
return SINK_FORMAT_SUPPORTED_DIRECTLY;
|
return SINK_FORMAT_SUPPORTED_DIRECTLY;
|
||||||
}
|
}
|
||||||
return SINK_FORMAT_UNSUPPORTED;
|
return SINK_FORMAT_UNSUPPORTED;
|
||||||
@ -767,7 +767,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
outputMode = OUTPUT_MODE_PASSTHROUGH;
|
outputMode = OUTPUT_MODE_PASSTHROUGH;
|
||||||
@Nullable
|
@Nullable
|
||||||
Pair<Integer, Integer> encodingAndChannelConfig =
|
Pair<Integer, Integer> encodingAndChannelConfig =
|
||||||
getEncodingAndChannelConfigForPassthrough(inputFormat, audioCapabilities);
|
audioCapabilities.getEncodingAndChannelConfigForPassthrough(inputFormat);
|
||||||
if (encodingAndChannelConfig == null) {
|
if (encodingAndChannelConfig == null) {
|
||||||
throw new ConfigurationException(
|
throw new ConfigurationException(
|
||||||
"Unable to configure passthrough for: " + inputFormat, inputFormat);
|
"Unable to configure passthrough for: " + inputFormat, inputFormat);
|
||||||
@ -1693,134 +1693,6 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
: writtenEncodedFrames;
|
: writtenEncodedFrames;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isPassthroughPlaybackSupported(
|
|
||||||
Format format, AudioCapabilities audioCapabilities) {
|
|
||||||
return getEncodingAndChannelConfigForPassthrough(format, audioCapabilities) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the encoding and channel config to use when configuring an {@link AudioTrack} in
|
|
||||||
* passthrough mode for the specified {@link Format}. Returns {@code null} if passthrough of the
|
|
||||||
* format is unsupported.
|
|
||||||
*
|
|
||||||
* @param format The {@link Format}.
|
|
||||||
* @param audioCapabilities The device audio capabilities.
|
|
||||||
* @return The encoding and channel config to use, or {@code null} if passthrough of the format is
|
|
||||||
* unsupported.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
private static Pair<Integer, Integer> getEncodingAndChannelConfigForPassthrough(
|
|
||||||
Format format, AudioCapabilities audioCapabilities) {
|
|
||||||
@C.Encoding
|
|
||||||
int encoding = MimeTypes.getEncoding(checkNotNull(format.sampleMimeType), format.codecs);
|
|
||||||
// Check for encodings that are known to work for passthrough with the implementation in this
|
|
||||||
// class. This avoids trying to use passthrough with an encoding where the device/app reports
|
|
||||||
// it's capable but it is untested or known to be broken (for example AAC-LC).
|
|
||||||
boolean supportedEncoding =
|
|
||||||
encoding == C.ENCODING_AC3
|
|
||||||
|| encoding == C.ENCODING_E_AC3
|
|
||||||
|| encoding == C.ENCODING_E_AC3_JOC
|
|
||||||
|| encoding == C.ENCODING_AC4
|
|
||||||
|| encoding == C.ENCODING_DTS
|
|
||||||
|| encoding == C.ENCODING_DTS_HD
|
|
||||||
|| encoding == C.ENCODING_DOLBY_TRUEHD;
|
|
||||||
if (!supportedEncoding) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (encoding == C.ENCODING_E_AC3_JOC
|
|
||||||
&& !audioCapabilities.supportsEncoding(C.ENCODING_E_AC3_JOC)) {
|
|
||||||
// E-AC3 receivers support E-AC3 JOC streams (but decode only the base layer).
|
|
||||||
encoding = C.ENCODING_E_AC3;
|
|
||||||
} else if (encoding == C.ENCODING_DTS_HD
|
|
||||||
&& !audioCapabilities.supportsEncoding(C.ENCODING_DTS_HD)) {
|
|
||||||
// DTS receivers support DTS-HD streams (but decode only the core layer).
|
|
||||||
encoding = C.ENCODING_DTS;
|
|
||||||
}
|
|
||||||
if (!audioCapabilities.supportsEncoding(encoding)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int channelCount;
|
|
||||||
if (encoding == C.ENCODING_E_AC3_JOC) {
|
|
||||||
// E-AC3 JOC is object based so the format channel count is arbitrary. From API 29 we can get
|
|
||||||
// the channel count for this encoding, but before then there is no way to query it so we
|
|
||||||
// assume 6 channel audio is supported.
|
|
||||||
if (Util.SDK_INT >= 29) {
|
|
||||||
// Default to 48 kHz if the format doesn't have a sample rate (for example, for chunkless
|
|
||||||
// HLS preparation). See [Internal: b/222127949].
|
|
||||||
int sampleRate = format.sampleRate != Format.NO_VALUE ? format.sampleRate : 48000;
|
|
||||||
channelCount =
|
|
||||||
getMaxSupportedChannelCountForPassthroughV29(C.ENCODING_E_AC3_JOC, sampleRate);
|
|
||||||
if (channelCount == 0) {
|
|
||||||
Log.w(TAG, "E-AC3 JOC encoding supported but no channel count supported");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
channelCount = 6;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
channelCount = format.channelCount;
|
|
||||||
if (channelCount > audioCapabilities.getMaxChannelCount()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int channelConfig = getChannelConfigForPassthrough(channelCount);
|
|
||||||
if (channelConfig == AudioFormat.CHANNEL_INVALID) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Pair.create(encoding, channelConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the maximum number of channels supported for passthrough playback of audio in the given
|
|
||||||
* format, or 0 if the format is unsupported.
|
|
||||||
*/
|
|
||||||
@RequiresApi(29)
|
|
||||||
private static int getMaxSupportedChannelCountForPassthroughV29(
|
|
||||||
@C.Encoding int encoding, int sampleRate) {
|
|
||||||
android.media.AudioAttributes audioAttributes =
|
|
||||||
new android.media.AudioAttributes.Builder()
|
|
||||||
.setUsage(android.media.AudioAttributes.USAGE_MEDIA)
|
|
||||||
.setContentType(android.media.AudioAttributes.CONTENT_TYPE_MOVIE)
|
|
||||||
.build();
|
|
||||||
// TODO(internal b/25994457): Query supported channel masks directly once it's supported.
|
|
||||||
for (int channelCount = 8; channelCount > 0; channelCount--) {
|
|
||||||
AudioFormat audioFormat =
|
|
||||||
new AudioFormat.Builder()
|
|
||||||
.setEncoding(encoding)
|
|
||||||
.setSampleRate(sampleRate)
|
|
||||||
.setChannelMask(Util.getAudioTrackChannelConfig(channelCount))
|
|
||||||
.build();
|
|
||||||
if (AudioTrack.isDirectPlaybackSupported(audioFormat, audioAttributes)) {
|
|
||||||
return channelCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getChannelConfigForPassthrough(int channelCount) {
|
|
||||||
if (Util.SDK_INT <= 28) {
|
|
||||||
// In passthrough mode the channel count used to configure the audio track doesn't affect how
|
|
||||||
// the stream is handled, except that some devices do overly-strict channel configuration
|
|
||||||
// checks. Therefore we override the channel count so that a known-working channel
|
|
||||||
// configuration is chosen in all cases. See [Internal: b/29116190].
|
|
||||||
if (channelCount == 7) {
|
|
||||||
channelCount = 8;
|
|
||||||
} else if (channelCount == 3 || channelCount == 4 || channelCount == 5) {
|
|
||||||
channelCount = 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workaround for Nexus Player not reporting support for mono passthrough. See
|
|
||||||
// [Internal: b/34268671].
|
|
||||||
if (Util.SDK_INT <= 26 && "fugu".equals(Util.DEVICE) && channelCount == 1) {
|
|
||||||
channelCount = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Util.getAudioTrackChannelConfig(channelCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean useOffloadedPlayback(Format format, AudioAttributes audioAttributes) {
|
private boolean useOffloadedPlayback(Format format, AudioAttributes audioAttributes) {
|
||||||
if (Util.SDK_INT < 29 || offloadMode == OFFLOAD_MODE_DISABLED) {
|
if (Util.SDK_INT < 29 || offloadMode == OFFLOAD_MODE_DISABLED) {
|
||||||
return false;
|
return false;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user