diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index a9eeea6be3..f5bf98716c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -189,13 +189,17 @@ public final class C { * Stream types for an {@link android.media.AudioTrack}. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({STREAM_TYPE_ALARM, STREAM_TYPE_MUSIC, STREAM_TYPE_NOTIFICATION, STREAM_TYPE_RING, - STREAM_TYPE_SYSTEM, STREAM_TYPE_VOICE_CALL}) + @IntDef({STREAM_TYPE_ALARM, STREAM_TYPE_DTMF, STREAM_TYPE_MUSIC, STREAM_TYPE_NOTIFICATION, + STREAM_TYPE_RING, STREAM_TYPE_SYSTEM, STREAM_TYPE_VOICE_CALL, STREAM_TYPE_USE_DEFAULT}) public @interface StreamType {} /** * @see AudioManager#STREAM_ALARM */ public static final int STREAM_TYPE_ALARM = AudioManager.STREAM_ALARM; + /** + * @see AudioManager#STREAM_DTMF + */ + public static final int STREAM_TYPE_DTMF = AudioManager.STREAM_DTMF; /** * @see AudioManager#STREAM_MUSIC */ @@ -216,11 +220,164 @@ public final class C { * @see AudioManager#STREAM_VOICE_CALL */ public static final int STREAM_TYPE_VOICE_CALL = AudioManager.STREAM_VOICE_CALL; + /** + * @see AudioManager#USE_DEFAULT_STREAM_TYPE + */ + public static final int STREAM_TYPE_USE_DEFAULT = AudioManager.USE_DEFAULT_STREAM_TYPE; /** * The default stream type used by audio renderers. */ public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC; + /** + * Content types for {@link com.google.android.exoplayer2.audio.AudioAttributes}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({CONTENT_TYPE_MOVIE, CONTENT_TYPE_MUSIC, CONTENT_TYPE_SONIFICATION, CONTENT_TYPE_SPEECH, + CONTENT_TYPE_UNKNOWN}) + public @interface AudioContentType {} + /** + * @see android.media.AudioAttributes#CONTENT_TYPE_MOVIE + */ + @SuppressWarnings("InlinedApi") + public static final int CONTENT_TYPE_MOVIE = android.media.AudioAttributes.CONTENT_TYPE_MOVIE; + /** + * @see android.media.AudioAttributes#CONTENT_TYPE_MUSIC + */ + @SuppressWarnings("InlinedApi") + public static final int CONTENT_TYPE_MUSIC = android.media.AudioAttributes.CONTENT_TYPE_MUSIC; + /** + * @see android.media.AudioAttributes#CONTENT_TYPE_SONIFICATION + */ + @SuppressWarnings("InlinedApi") + public static final int CONTENT_TYPE_SONIFICATION = + android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION; + /** + * @see android.media.AudioAttributes#CONTENT_TYPE_SPEECH + */ + @SuppressWarnings("InlinedApi") + public static final int CONTENT_TYPE_SPEECH = + android.media.AudioAttributes.CONTENT_TYPE_SPEECH; + /** + * @see android.media.AudioAttributes#CONTENT_TYPE_UNKNOWN + */ + @SuppressWarnings("InlinedApi") + public static final int CONTENT_TYPE_UNKNOWN = + android.media.AudioAttributes.CONTENT_TYPE_UNKNOWN; + + /** + * Flags for {@link com.google.android.exoplayer2.audio.AudioAttributes}. + *
+ * Note that {@code FLAG_HW_AV_SYNC} is not available because the player takes care of setting the + * flag when tunneling is enabled via a track selector. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_AUDIBILITY_ENFORCED}) + public @interface AudioFlags {} + /** + * @see android.media.AudioAttributes#FLAG_AUDIBILITY_ENFORCED + */ + @SuppressWarnings("InlinedApi") + public static final int FLAG_AUDIBILITY_ENFORCED = + android.media.AudioAttributes.FLAG_AUDIBILITY_ENFORCED; + + /** + * Usage types for {@link com.google.android.exoplayer2.audio.AudioAttributes}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({USAGE_ALARM, USAGE_ASSISTANCE_ACCESSIBILITY, USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, + USAGE_ASSISTANCE_SONIFICATION, USAGE_GAME, USAGE_MEDIA, USAGE_NOTIFICATION, + USAGE_NOTIFICATION_COMMUNICATION_DELAYED, USAGE_NOTIFICATION_COMMUNICATION_INSTANT, + USAGE_NOTIFICATION_COMMUNICATION_REQUEST, USAGE_NOTIFICATION_EVENT, + USAGE_NOTIFICATION_RINGTONE, USAGE_UNKNOWN, USAGE_VOICE_COMMUNICATION, + USAGE_VOICE_COMMUNICATION_SIGNALLING}) + public @interface AudioUsage {} + /** + * @see android.media.AudioAttributes#USAGE_ALARM + */ + @SuppressWarnings("InlinedApi") + public static final int USAGE_ALARM = android.media.AudioAttributes.USAGE_ALARM; + /** + * @see android.media.AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY + */ + @SuppressWarnings("InlinedApi") + public static final int USAGE_ASSISTANCE_ACCESSIBILITY = + android.media.AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY; + /** + * @see android.media.AudioAttributes#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE + */ + @SuppressWarnings("InlinedApi") + public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = + android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE; + /** + * @see android.media.AudioAttributes#USAGE_ASSISTANCE_SONIFICATION + */ + @SuppressWarnings("InlinedApi") + public static final int USAGE_ASSISTANCE_SONIFICATION = + android.media.AudioAttributes.USAGE_ASSISTANCE_SONIFICATION; + /** + * @see android.media.AudioAttributes#USAGE_GAME + */ + @SuppressWarnings("InlinedApi") + public static final int USAGE_GAME = android.media.AudioAttributes.USAGE_GAME; + /** + * @see android.media.AudioAttributes#USAGE_MEDIA + */ + @SuppressWarnings("InlinedApi") + public static final int USAGE_MEDIA = android.media.AudioAttributes.USAGE_MEDIA; + /** + * @see android.media.AudioAttributes#USAGE_NOTIFICATION + */ + @SuppressWarnings("InlinedApi") + public static final int USAGE_NOTIFICATION = android.media.AudioAttributes.USAGE_NOTIFICATION; + /** + * @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_DELAYED + */ + @SuppressWarnings("InlinedApi") + public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = + android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED; + /** + * @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_INSTANT + */ + @SuppressWarnings("InlinedApi") + public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = + android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT; + /** + * @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_REQUEST + */ + @SuppressWarnings("InlinedApi") + public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = + android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST; + /** + * @see android.media.AudioAttributes#USAGE_NOTIFICATION_EVENT + */ + @SuppressWarnings("InlinedApi") + public static final int USAGE_NOTIFICATION_EVENT = + android.media.AudioAttributes.USAGE_NOTIFICATION_EVENT; + /** + * @see android.media.AudioAttributes#USAGE_NOTIFICATION_RINGTONE + */ + @SuppressWarnings("InlinedApi") + public static final int USAGE_NOTIFICATION_RINGTONE = + android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; + /** + * @see android.media.AudioAttributes#USAGE_UNKNOWN + */ + @SuppressWarnings("InlinedApi") + public static final int USAGE_UNKNOWN = android.media.AudioAttributes.USAGE_UNKNOWN; + /** + * @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION + */ + @SuppressWarnings("InlinedApi") + public static final int USAGE_VOICE_COMMUNICATION = + android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION; + /** + * @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION_SIGNALLING + */ + @SuppressWarnings("InlinedApi") + public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = + android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING; + /** * Flags which can apply to a buffer containing a media sample. */ @@ -498,16 +655,25 @@ public final class C { /** * A type of a message that can be passed to an audio {@link Renderer} via * {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object - * should be one of the integer stream types in {@link C.StreamType}, and will specify the stream - * type of the underlying {@link android.media.AudioTrack}. See also - * {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}. If the stream type - * is not set, audio renderers use {@link #STREAM_TYPE_DEFAULT}. + * should be an {@link com.google.android.exoplayer2.audio.AudioAttributes} instance that will + * configure the underlying audio track. If not set, the default audio attributes will be used. + * They are suitable for general media playback. *
- * Note that when the stream type changes, the AudioTrack must be reinitialized, which can - * introduce a brief gap in audio output. Note also that tracks in the same audio session must - * share the same routing, so a new audio session id will be generated. + * Setting the audio attributes during playback may introduce a short gap in audio output as the + * audio track is recreated. A new audio session id will also be generated. + *
+ * If tunneling is enabled by the track selector, the specified audio attributes will be ignored, + * but they will take effect if audio is later played without tunneling. + *
+ * If the device is running a build before platform API version 21, audio attributes cannot be set + * directly on the underlying audio track. In this case, the usage will be mapped onto an + * equivalent stream type using {@link Util#getStreamTypeForAudioUsage(int)}. + *
+ * To get audio attributes that are equivalent to a legacy stream type, pass the stream type to + * {@link Util#getAudioUsageForStreamType(int)} and use the returned {@link C.AudioUsage} to build + * an audio attributes instance. */ - public static final int MSG_SET_STREAM_TYPE = 3; + public static final int MSG_SET_AUDIO_ATTRIBUTES = 3; /** * The type of a message that can be passed to a {@link MediaCodec}-based video {@link Renderer} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 97cc7d349f..054d3e38b9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -27,6 +27,7 @@ import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.TextureView; +import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.metadata.Metadata; @@ -37,6 +38,7 @@ import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.TextRenderer; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelector; +import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoRendererEventListener; import java.util.List; @@ -105,8 +107,7 @@ public class SimpleExoPlayer implements ExoPlayer { private DecoderCounters videoDecoderCounters; private DecoderCounters audioDecoderCounters; private int audioSessionId; - @C.StreamType - private int audioStreamType; + private AudioAttributes audioAttributes; private float audioVolume; protected SimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector, @@ -136,7 +137,7 @@ public class SimpleExoPlayer implements ExoPlayer { // Set initial values. audioVolume = 1; audioSessionId = C.AUDIO_SESSION_ID_UNSET; - audioStreamType = C.STREAM_TYPE_DEFAULT; + audioAttributes = AudioAttributes.DEFAULT; videoScalingMode = C.VIDEO_SCALING_MODE_DEFAULT; // Build the player and associated objects. @@ -293,33 +294,70 @@ public class SimpleExoPlayer implements ExoPlayer { } /** - * Sets the stream type for audio playback (see {@link C.StreamType} and - * {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}). If the stream type - * is not set, audio renderers use {@link C#STREAM_TYPE_DEFAULT}. + * Sets the stream type for audio playback, used by the underlying audio track. *
- * Note that when the stream type changes, the AudioTrack must be reinitialized, which can - * introduce a brief gap in audio output. Note also that tracks in the same audio session must - * share the same routing, so a new audio session id will be generated. + * Setting the stream type during playback may introduce a short gap in audio output as the audio + * track is recreated. A new audio session id will also be generated. + *
+ * Calling this method overwrites any attributes set previously by calling + * {@link #setAudioAttributes(AudioAttributes)}. * - * @param audioStreamType The stream type for audio playback. + * @deprecated Use {@link #setAudioAttributes(AudioAttributes)}. + * @param streamType The stream type for audio playback. */ - public void setAudioStreamType(@C.StreamType int audioStreamType) { - this.audioStreamType = audioStreamType; + @Deprecated + public void setAudioStreamType(@C.StreamType int streamType) { + @C.AudioUsage int usage = Util.getAudioUsageForStreamType(streamType); + @C.AudioContentType int contentType = Util.getAudioContentTypeForStreamType(streamType); + AudioAttributes audioAttributes = + new AudioAttributes.Builder().setUsage(usage).setContentType(contentType).build(); + setAudioAttributes(audioAttributes); + } + + /** + * Returns the stream type for audio playback. + * + * @deprecated Use {@link #getAudioAttributes()}. + */ + @Deprecated + public @C.StreamType int getAudioStreamType() { + return Util.getStreamTypeForAudioUsage(audioAttributes.usage); + } + + /** + * Sets the attributes for audio playback, used by the underlying audio track. If not set, the + * default audio attributes will be used. They are suitable for general media playback. + *
+ * Setting the audio attributes during playback may introduce a short gap in audio output as the + * audio track is recreated. A new audio session id will also be generated. + *
+ * If tunneling is enabled by the track selector, the specified audio attributes will be ignored, + * but they will take effect if audio is later played without tunneling. + *
+ * If the device is running a build before platform API version 21, audio attributes cannot be set + * directly on the underlying audio track. In this case, the usage will be mapped onto an + * equivalent stream type using {@link Util#getStreamTypeForAudioUsage(int)}. + * + * @param audioAttributes The attributes to use for audio playback. + */ + public void setAudioAttributes(AudioAttributes audioAttributes) { + this.audioAttributes = audioAttributes; ExoPlayerMessage[] messages = new ExoPlayerMessage[audioRendererCount]; int count = 0; for (Renderer renderer : renderers) { if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) { - messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_STREAM_TYPE, audioStreamType); + messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_AUDIO_ATTRIBUTES, + audioAttributes); } } player.sendMessages(messages); } /** - * Returns the stream type for audio playback. + * Returns the attributes for audio playback. */ - public @C.StreamType int getAudioStreamType() { - return audioStreamType; + public AudioAttributes getAudioAttributes() { + return audioAttributes; } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioAttributes.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioAttributes.java new file mode 100644 index 0000000000..337200da8f --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioAttributes.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.audio; + +import android.annotation.TargetApi; +import com.google.android.exoplayer2.C; + +/** + * Attributes for audio playback, which configure the underlying platform + * {@link android.media.AudioTrack}. + *
+ * To set the audio attributes, create an instance using the {@link Builder} and either pass it to + * {@link com.google.android.exoplayer2.SimpleExoPlayer#setAudioAttributes(AudioAttributes)} or + * send a message of type {@link C#MSG_SET_AUDIO_ATTRIBUTES} to the audio renderers. + *
+ * This class is based on {@link android.media.AudioAttributes}, but can be used on all supported + * API versions. + */ +public final class AudioAttributes { + + public static final AudioAttributes DEFAULT = new Builder().build(); + + /** + * Builder for {@link AudioAttributes}. + */ + public static final class Builder { + + @C.AudioContentType + private int contentType; + @C.AudioFlags + private int flags; + @C.AudioUsage + private int usage; + + /** + * Creates a new builder for {@link AudioAttributes}. + *
+ * By default the content type is {@link C#CONTENT_TYPE_UNKNOWN}, usage is + * {@link C#USAGE_MEDIA}, and no flags are set. + */ + public Builder() { + contentType = C.CONTENT_TYPE_UNKNOWN; + flags = 0; + usage = C.USAGE_MEDIA; + } + + /** + * @see android.media.AudioAttributes.Builder#setContentType(int) + */ + public Builder setContentType(@C.AudioContentType int contentType) { + this.contentType = contentType; + return this; + } + + /** + * @see android.media.AudioAttributes.Builder#setFlags(int) + */ + public Builder setFlags(@C.AudioFlags int flags) { + this.flags = flags; + return this; + } + + /** + * @see android.media.AudioAttributes.Builder#setUsage(int) + */ + public Builder setUsage(@C.AudioUsage int usage) { + this.usage = usage; + return this; + } + + /** + * Creates an {@link AudioAttributes} instance from this builder. + */ + public AudioAttributes build() { + return new AudioAttributes(contentType, flags, usage); + } + + } + + @C.AudioContentType + public final int contentType; + @C.AudioFlags + public final int flags; + @C.AudioUsage + public final int usage; + + private android.media.AudioAttributes audioAttributesV21; + + private AudioAttributes(@C.AudioContentType int contentType, @C.AudioFlags int flags, + @C.AudioUsage int usage) { + this.contentType = contentType; + this.flags = flags; + this.usage = usage; + } + + @TargetApi(21) + /* package */ android.media.AudioAttributes getAudioAttributesV21() { + if (audioAttributesV21 == null) { + audioAttributesV21 = new android.media.AudioAttributes.Builder() + .setContentType(contentType) + .setFlags(flags) + .setUsage(usage) + .build(); + } + return audioAttributesV21; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + AudioAttributes other = (AudioAttributes) obj; + return this.contentType == other.contentType && this.flags == other.flags + && this.usage == other.usage; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + contentType; + result = 31 * result + flags; + result = 31 * result + usage; + return result; + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java index 92838e34b0..f18e40dec4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java @@ -17,8 +17,8 @@ package com.google.android.exoplayer2.audio; import android.annotation.SuppressLint; import android.annotation.TargetApi; -import android.media.AudioAttributes; import android.media.AudioFormat; +import android.media.AudioManager; import android.media.AudioTimestamp; import android.os.ConditionVariable; import android.os.SystemClock; @@ -40,9 +40,9 @@ import java.util.LinkedList; *
* Before starting playback, specify the input format by calling * {@link #configure(String, int, int, int, int)}. Optionally call {@link #setAudioSessionId(int)}, - * {@link #setStreamType(int)}, {@link #enableTunnelingV21(int)} and {@link #disableTunneling()} - * to configure audio playback. These methods may be called after writing data to the track, in - * which case it will be reinitialized as required. + * {@link #setAudioAttributes(AudioAttributes)}, {@link #enableTunnelingV21(int)} and + * {@link #disableTunneling()} to configure audio playback. These methods may be called after + * writing data to the track, in which case it will be reinitialized as required. *
* Call {@link #handleBuffer(ByteBuffer, long)} to write data, and {@link #handleDiscontinuity()} * when the data being fed is discontinuous. Call {@link #play()} to start playing the written data. @@ -299,8 +299,7 @@ public final class AudioTrack { private int encoding; @C.Encoding private int outputEncoding; - @C.StreamType - private int streamType; + private AudioAttributes audioAttributes; private boolean passthrough; private int bufferSize; private long bufferSizeUs; @@ -384,7 +383,7 @@ public final class AudioTrack { playheadOffsets = new long[MAX_PLAYHEAD_OFFSET_COUNT]; volume = 1.0f; startMediaTimeState = START_NOT_SET; - streamType = C.STREAM_TYPE_DEFAULT; + audioAttributes = AudioAttributes.DEFAULT; audioSessionId = C.AUDIO_SESSION_ID_UNSET; playbackParameters = PlaybackParameters.DEFAULT; drainingAudioProcessorIndex = C.INDEX_UNSET; @@ -634,19 +633,7 @@ public final class AudioTrack { // initialization of the audio track to fail. releasingConditionVariable.block(); - if (tunneling) { - audioTrack = createHwAvSyncAudioTrackV21(sampleRate, channelConfig, outputEncoding, - bufferSize, audioSessionId); - } else if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) { - audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, - outputEncoding, bufferSize, MODE_STREAM); - } else { - // Re-attach to the same audio session. - audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, - outputEncoding, bufferSize, MODE_STREAM, audioSessionId); - } - checkAudioTrackInitialized(); - + audioTrack = initializeAudioTrack(); int audioSessionId = audioTrack.getAudioSessionId(); if (enablePreV21AudioSessionWorkaround) { if (Util.SDK_INT < 21) { @@ -657,12 +644,7 @@ public final class AudioTrack { releaseKeepSessionIdAudioTrack(); } if (keepSessionIdAudioTrack == null) { - int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE. - int channelConfig = AudioFormat.CHANNEL_OUT_MONO; - @C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT; - int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback. - keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate, - channelConfig, encoding, bufferSize, MODE_STATIC, audioSessionId); + keepSessionIdAudioTrack = initializeKeepSessionIdAudioTrack(audioSessionId); } } } @@ -1021,23 +1003,23 @@ public final class AudioTrack { } /** - * Sets the stream type for audio track. If the stream type has changed and if the audio track + * Sets the attributes for audio playback. If the attributes have changed and if the audio track * is not configured for use with tunneling, then the audio track is reset and the audio session * id is cleared. *
- * If the audio track is configured for use with tunneling then the stream type is ignored, the - * audio track is not reset and the audio session id is not cleared. The passed stream type will - * be used if the audio track is later re-configured into non-tunneled mode. + * If the audio track is configured for use with tunneling then the audio attributes are ignored. + * The audio track is not reset and the audio session id is not cleared. The passed attributes + * will be used if the audio track is later re-configured into non-tunneled mode. * - * @param streamType The {@link C.StreamType} to use for audio output. + * @param audioAttributes The attributes for audio playback. */ - public void setStreamType(@C.StreamType int streamType) { - if (this.streamType == streamType) { + public void setAudioAttributes(AudioAttributes audioAttributes) { + if (this.audioAttributes.equals(audioAttributes)) { return; } - this.streamType = streamType; + this.audioAttributes = audioAttributes; if (tunneling) { - // The stream type is ignored in tunneling mode, so no need to reset. + // The audio attributes are ignored in tunneling mode, so no need to reset. return; } reset(); @@ -1333,31 +1315,6 @@ public final class AudioTrack { } } - /** - * Checks that {@link #audioTrack} has been successfully initialized. If it has then calling this - * method is a no-op. If it hasn't then {@link #audioTrack} is released and set to null, and an - * exception is thrown. - * - * @throws InitializationException If {@link #audioTrack} has not been successfully initialized. - */ - private void checkAudioTrackInitialized() throws InitializationException { - int state = audioTrack.getState(); - if (state == STATE_INITIALIZED) { - return; - } - // The track is not successfully initialized. Release and null the track. - try { - audioTrack.release(); - } catch (Exception e) { - // The track has already failed to initialize, so it wouldn't be that surprising if release - // were to fail too. Swallow the exception. - } finally { - audioTrack = null; - } - - throw new InitializationException(state, sampleRate, channelConfig, bufferSize); - } - private boolean isInitialized() { return audioTrack != null; } @@ -1408,24 +1365,65 @@ public final class AudioTrack { && audioTrack.getPlaybackHeadPosition() == 0; } - /** - * Instantiates an {@link android.media.AudioTrack} to be used with tunneling video playback. - */ + private android.media.AudioTrack initializeAudioTrack() throws InitializationException { + android.media.AudioTrack audioTrack; + if (Util.SDK_INT >= 21) { + audioTrack = createAudioTrackV21(); + } else { + int streamType = Util.getStreamTypeForAudioUsage(audioAttributes.usage); + if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) { + audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, + outputEncoding, bufferSize, MODE_STREAM); + } else { + // Re-attach to the same audio session. + audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, + outputEncoding, bufferSize, MODE_STREAM, audioSessionId); + } + } + + int state = audioTrack.getState(); + if (state != STATE_INITIALIZED) { + try { + audioTrack.release(); + } catch (Exception e) { + // The track has already failed to initialize, so it wouldn't be that surprising if release + // were to fail too. Swallow the exception. + } + throw new InitializationException(state, sampleRate, channelConfig, bufferSize); + } + return audioTrack; + } + @TargetApi(21) - private static android.media.AudioTrack createHwAvSyncAudioTrackV21(int sampleRate, - int channelConfig, int encoding, int bufferSize, int sessionId) { - AudioAttributes attributesBuilder = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_MEDIA) - .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE) - .setFlags(AudioAttributes.FLAG_HW_AV_SYNC) - .build(); + private android.media.AudioTrack createAudioTrackV21() { + android.media.AudioAttributes attributes; + if (tunneling) { + attributes = new android.media.AudioAttributes.Builder() + .setContentType(android.media.AudioAttributes.CONTENT_TYPE_MOVIE) + .setFlags(android.media.AudioAttributes.FLAG_HW_AV_SYNC) + .setUsage(android.media.AudioAttributes.USAGE_MEDIA) + .build(); + } else { + attributes = audioAttributes.getAudioAttributesV21(); + } AudioFormat format = new AudioFormat.Builder() .setChannelMask(channelConfig) - .setEncoding(encoding) + .setEncoding(outputEncoding) .setSampleRate(sampleRate) .build(); - return new android.media.AudioTrack(attributesBuilder, format, bufferSize, MODE_STREAM, - sessionId); + int audioSessionId = this.audioSessionId != C.AUDIO_SESSION_ID_UNSET ? this.audioSessionId + : AudioManager.AUDIO_SESSION_ID_GENERATE; + return new android.media.AudioTrack(attributes, format, bufferSize, MODE_STREAM, + audioSessionId); + } + + private android.media.AudioTrack initializeKeepSessionIdAudioTrack(int audioSessionId) { + int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE. + int channelConfig = AudioFormat.CHANNEL_OUT_MONO; + @C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT; + int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback. + return new android.media.AudioTrack(C.STREAM_TYPE_DEFAULT, sampleRate, channelConfig, encoding, + bufferSize, MODE_STATIC, audioSessionId); } @C.Encoding diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 48c7462b03..4d97c292ac 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -399,9 +399,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media case C.MSG_SET_VOLUME: audioTrack.setVolume((Float) message); break; - case C.MSG_SET_STREAM_TYPE: - @C.StreamType int streamType = (Integer) message; - audioTrack.setStreamType(streamType); + case C.MSG_SET_AUDIO_ATTRIBUTES: + AudioAttributes audioAttributes = (AudioAttributes) message; + audioTrack.setAudioAttributes(audioAttributes); break; default: super.handleMessage(messageType, message); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java index ddb870f6ff..a16a3f3ca2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -595,9 +595,9 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements case C.MSG_SET_VOLUME: audioTrack.setVolume((Float) message); break; - case C.MSG_SET_STREAM_TYPE: - @C.StreamType int streamType = (Integer) message; - audioTrack.setStreamType(streamType); + case C.MSG_SET_AUDIO_ATTRIBUTES: + AudioAttributes audioAttributes = (AudioAttributes) message; + audioTrack.setAudioAttributes(audioAttributes); break; default: super.handleMessage(messageType, message); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index a65a9cbf44..c00d7fa36c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -796,6 +796,85 @@ public final class Util { } } + /** + * Returns the {@link C.AudioUsage} corresponding to the specified {@link C.StreamType}. + */ + @C.AudioUsage + public static int getAudioUsageForStreamType(@C.StreamType int streamType) { + switch (streamType) { + case C.STREAM_TYPE_ALARM: + return C.USAGE_ALARM; + case C.STREAM_TYPE_DTMF: + return C.USAGE_VOICE_COMMUNICATION_SIGNALLING; + case C.STREAM_TYPE_NOTIFICATION: + return C.USAGE_NOTIFICATION; + case C.STREAM_TYPE_RING: + return C.USAGE_NOTIFICATION_RINGTONE; + case C.STREAM_TYPE_SYSTEM: + return C.USAGE_ASSISTANCE_SONIFICATION; + case C.STREAM_TYPE_VOICE_CALL: + return C.USAGE_VOICE_COMMUNICATION; + case C.STREAM_TYPE_USE_DEFAULT: + case C.STREAM_TYPE_MUSIC: + default: + return C.USAGE_MEDIA; + } + } + + /** + * Returns the {@link C.AudioContentType} corresponding to the specified {@link C.StreamType}. + */ + @C.AudioContentType + public static int getAudioContentTypeForStreamType(@C.StreamType int streamType) { + switch (streamType) { + case C.STREAM_TYPE_ALARM: + case C.STREAM_TYPE_DTMF: + case C.STREAM_TYPE_NOTIFICATION: + case C.STREAM_TYPE_RING: + case C.STREAM_TYPE_SYSTEM: + return C.CONTENT_TYPE_SONIFICATION; + case C.STREAM_TYPE_VOICE_CALL: + return C.CONTENT_TYPE_SPEECH; + case C.STREAM_TYPE_USE_DEFAULT: + case C.STREAM_TYPE_MUSIC: + default: + return C.CONTENT_TYPE_MUSIC; + } + } + + /** + * Returns the {@link C.StreamType} corresponding to the specified {@link C.AudioUsage}. + */ + @C.StreamType + public static int getStreamTypeForAudioUsage(@C.AudioUsage int usage) { + switch (usage) { + case C.USAGE_MEDIA: + case C.USAGE_GAME: + case C.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE: + return C.STREAM_TYPE_MUSIC; + case C.USAGE_ASSISTANCE_SONIFICATION: + return C.STREAM_TYPE_SYSTEM; + case C.USAGE_VOICE_COMMUNICATION: + return C.STREAM_TYPE_VOICE_CALL; + case C.USAGE_VOICE_COMMUNICATION_SIGNALLING: + return C.STREAM_TYPE_DTMF; + case C.USAGE_ALARM: + return C.STREAM_TYPE_ALARM; + case C.USAGE_NOTIFICATION_RINGTONE: + return C.STREAM_TYPE_RING; + case C.USAGE_NOTIFICATION: + case C.USAGE_NOTIFICATION_COMMUNICATION_REQUEST: + case C.USAGE_NOTIFICATION_COMMUNICATION_INSTANT: + case C.USAGE_NOTIFICATION_COMMUNICATION_DELAYED: + case C.USAGE_NOTIFICATION_EVENT: + return C.STREAM_TYPE_NOTIFICATION; + case C.USAGE_ASSISTANCE_ACCESSIBILITY: + case C.USAGE_UNKNOWN: + default: + return C.STREAM_TYPE_DEFAULT; + } + } + /** * Makes a best guess to infer the type from a {@link Uri}. *