diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ef5a1df134..90af85059c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -32,6 +32,7 @@ * Increase `AudioTrack` buffer sizes to the theoretical maximum required for each encoding for passthrough playbacks ([#3803](https://github.com/google/ExoPlayer/issues/3803)). + * Add support for attaching auxiliary audio effects to the `AudioTrack`. * Allow apps to pass a `CacheKeyFactory` for setting custom cache keys when creating a `CacheDataSource`. * Turned on Java 8 compiler support for the ExoPlayer library. Apps that depend 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 7d98b29ab0..87499a9cb1 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 @@ -24,6 +24,7 @@ import android.media.MediaFormat; import android.support.annotation.IntDef; import android.view.Surface; import com.google.android.exoplayer2.PlayerMessage.Target; +import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -725,6 +726,13 @@ public final class C { */ public static final int MSG_SET_SCALING_MODE = 4; + /** + * A type of a message that can be passed to an audio {@link Renderer} via {@link + * ExoPlayer#createMessage(Target)}. The message payload should be an {@link AuxEffectInfo} + * instance representing an auxiliary audio effect for the underlying audio track. + */ + public static final int MSG_SET_AUX_EFFECT_INFO = 5; + /** * Applications or extensions may define custom {@code MSG_*} constants that can be passed to * {@link Renderer}s. These custom constants must be greater than or equal to this value. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index 748e31d77f..87aec0c253 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -24,6 +24,7 @@ import android.view.SurfaceView; import android.view.TextureView; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioListener; +import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; @@ -119,6 +120,12 @@ public interface Player { /** Returns the audio session identifier, or {@link C#AUDIO_SESSION_ID_UNSET} if not set. */ int getAudioSessionId(); + /** Sets information on an auxiliary audio effect to attach to the underlying audio track. */ + void setAuxEffectInfo(AuxEffectInfo auxEffectInfo); + + /** Detaches any previously attached auxiliary audio effect from the underlying audio track. */ + void clearAuxEffectInfo(); + /** * Sets the audio volume, with 0 being silence and 1 being unity gain. * 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 ed47c85d46..055cf1de17 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 @@ -35,6 +35,7 @@ import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioFocusManager; import com.google.android.exoplayer2.audio.AudioListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener; +import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager; @@ -450,6 +451,24 @@ public class SimpleExoPlayer return audioSessionId; } + @Override + public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo) { + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) { + player + .createMessage(renderer) + .setType(C.MSG_SET_AUX_EFFECT_INFO) + .setPayload(auxEffectInfo) + .send(); + } + } + } + + @Override + public void clearAuxEffectInfo() { + setAuxEffectInfo(new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, /* sendLevel= */ 0f)); + } + @Override public void setVolume(float audioVolume) { audioVolume = Util.constrainValue(audioVolume, /* min= */ 0, /* max= */ 1); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java index 07584d575e..0db52daa12 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java @@ -283,11 +283,12 @@ public interface AudioSink { */ void setAudioAttributes(AudioAttributes audioAttributes); - /** - * Sets the audio session id. - */ + /** Sets the audio session id. */ void setAudioSessionId(int audioSessionId); + /** Sets the auxiliary effect. */ + void setAuxEffectInfo(AuxEffectInfo auxEffectInfo); + /** * Enables tunneling, if possible. The sink is reset if tunneling was previously disabled or if * the audio session id has changed. Enabling tunneling is only possible if the sink is based on a diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AuxEffectInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AuxEffectInfo.java new file mode 100644 index 0000000000..7462a9c4b0 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AuxEffectInfo.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018 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.media.AudioTrack; +import android.media.audiofx.AudioEffect; +import android.support.annotation.Nullable; + +/** + * Represents auxiliary effect information, which can be used to attach an auxiliary effect to an + * underlying {@link AudioTrack}. + * + *
Auxiliary effects can only be applied if the application has the {@code + * android.permission.MODIFY_AUDIO_SETTINGS} permission. Apps are responsible for retaining the + * associated audio effect instance and releasing it when it's no longer needed. See the + * documentation of {@link AudioEffect} for more information. + */ +public final class AuxEffectInfo { + + /** Value for {@link #effectId} representing no auxiliary effect. */ + public static final int NO_AUX_EFFECT_ID = 0; + + /** + * The identifier of the effect, or {@link #NO_AUX_EFFECT_ID} if there is no effect. + * + * @see android.media.AudioTrack#attachAuxEffect(int) + */ + public final int effectId; + /** + * The send level for the effect. + * + * @see android.media.AudioTrack#setAuxEffectSendLevel(float) + */ + public final float sendLevel; + + /** + * Creates an instance with the given effect identifier and send level. + * + * @param effectId The effect identifier. This is the value returned by {@link + * AudioEffect#getId()} on the effect, or {@value NO_AUX_EFFECT_ID} which represents no + * effect. This value is passed to {@link AudioTrack#attachAuxEffect(int)} on the underlying + * audio track. + * @param sendLevel The send level for the effect, where 0 represents no effect and a value of 1 + * is full send. If {@code effectId} is not {@value #NO_AUX_EFFECT_ID}, this value is passed + * to {@link AudioTrack#setAuxEffectSendLevel(float)} on the underlying audio track. + */ + public AuxEffectInfo(int effectId, float sendLevel) { + this.effectId = effectId; + this.sendLevel = sendLevel; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AuxEffectInfo auxEffectInfo = (AuxEffectInfo) o; + return effectId == auxEffectInfo.effectId + && Float.compare(auxEffectInfo.sendLevel, sendLevel) == 0; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + effectId; + result = 31 * result + Float.floatToIntBits(sendLevel); + return result; + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 4c54297229..aed4c63c75 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -280,6 +280,7 @@ public final class DefaultAudioSink implements AudioSink { private boolean playing; private int audioSessionId; + private AuxEffectInfo auxEffectInfo; private boolean tunneling; private long lastFeedElapsedRealtimeMs; @@ -356,6 +357,7 @@ public final class DefaultAudioSink implements AudioSink { startMediaTimeState = START_NOT_SET; audioAttributes = AudioAttributes.DEFAULT; audioSessionId = C.AUDIO_SESSION_ID_UNSET; + auxEffectInfo = new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, 0f); playbackParameters = PlaybackParameters.DEFAULT; drainingAudioProcessorIndex = C.INDEX_UNSET; activeAudioProcessors = new AudioProcessor[0]; @@ -547,6 +549,11 @@ public final class DefaultAudioSink implements AudioSink { audioTrackPositionTracker.setAudioTrack( audioTrack, outputEncoding, outputPcmFrameSize, bufferSize); setVolumeInternal(); + + if (auxEffectInfo.effectId != AuxEffectInfo.NO_AUX_EFFECT_ID) { + audioTrack.attachAuxEffect(auxEffectInfo.effectId); + audioTrack.setAuxEffectSendLevel(auxEffectInfo.sendLevel); + } } @Override @@ -867,6 +874,24 @@ public final class DefaultAudioSink implements AudioSink { } } + @Override + public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo) { + if (this.auxEffectInfo.equals(auxEffectInfo)) { + return; + } + int effectId = auxEffectInfo.effectId; + float sendLevel = auxEffectInfo.sendLevel; + if (audioTrack != null) { + if (this.auxEffectInfo.effectId != effectId) { + audioTrack.attachAuxEffect(effectId); + } + if (effectId != AuxEffectInfo.NO_AUX_EFFECT_ID) { + audioTrack.setAuxEffectSendLevel(sendLevel); + } + } + this.auxEffectInfo = auxEffectInfo; + } + @Override public void enableTunnelingV21(int tunnelingAudioSessionId) { Assertions.checkState(Util.SDK_INT >= 21); 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 6141e2879a..e40620622e 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 @@ -60,6 +60,9 @@ import java.util.List; *