Commit f1e3d3f2 by Oliver Woodman

Enable tunneling end-to-end

- Tunneling can be enabled by calling:
  trackSelector.setTunnelingAudioSessionId(
      C.generateAudioSessionIdV21(this));

- If enabled, tunneling is automatically used when the renderers
  and track selection combination support it. Tunneling is
  automatically turned on and off through playlists if the support
  changes.

Issue: #1688
parent c828d9b0
...@@ -28,6 +28,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -28,6 +28,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
private final int trackType; private final int trackType;
private RendererConfiguration configuration;
private int index; private int index;
private int state; private int state;
private SampleStream stream; private SampleStream stream;
...@@ -70,9 +71,11 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -70,9 +71,11 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
} }
@Override @Override
public final void enable(Format[] formats, SampleStream stream, long positionUs, boolean joining, public final void enable(RendererConfiguration configuration, Format[] formats,
long offsetUs) throws ExoPlaybackException { SampleStream stream, long positionUs, boolean joining, long offsetUs)
throws ExoPlaybackException {
Assertions.checkState(state == STATE_DISABLED); Assertions.checkState(state == STATE_DISABLED);
this.configuration = configuration;
state = STATE_ENABLED; state = STATE_ENABLED;
onEnabled(joining); onEnabled(joining);
replaceStream(formats, stream, offsetUs); replaceStream(formats, stream, offsetUs);
...@@ -238,9 +241,14 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -238,9 +241,14 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
// Methods to be called by subclasses. // Methods to be called by subclasses.
/** /**
* Returns the configuration set when the renderer was most recently enabled.
*/
protected final RendererConfiguration getConfiguration() {
return configuration;
}
/**
* Returns the index of the renderer within the player. * Returns the index of the renderer within the player.
*
* @return The index of the renderer within the player.
*/ */
protected final int getIndex() { protected final int getIndex() {
return index; return index;
......
...@@ -35,7 +35,6 @@ import com.google.android.exoplayer2.util.MediaClock; ...@@ -35,7 +35,6 @@ import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.PriorityHandlerThread; import com.google.android.exoplayer2.util.PriorityHandlerThread;
import com.google.android.exoplayer2.util.StandaloneMediaClock; import com.google.android.exoplayer2.util.StandaloneMediaClock;
import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
/** /**
...@@ -1149,16 +1148,15 @@ import java.io.IOException; ...@@ -1149,16 +1148,15 @@ import java.io.IOException;
} }
if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) { if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) {
TrackSelectionArray oldTrackSelections = readingPeriodHolder.trackSelectorResult.selections; TrackSelectorResult oldTrackSelectorResult = readingPeriodHolder.trackSelectorResult;
readingPeriodHolder = readingPeriodHolder.next; readingPeriodHolder = readingPeriodHolder.next;
TrackSelectionArray newTrackSelections = readingPeriodHolder.trackSelectorResult.selections; TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.trackSelectorResult;
boolean initialDiscontinuity = boolean initialDiscontinuity =
readingPeriodHolder.mediaPeriod.readDiscontinuity() != C.TIME_UNSET; readingPeriodHolder.mediaPeriod.readDiscontinuity() != C.TIME_UNSET;
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i]; Renderer renderer = renderers[i];
TrackSelection oldSelection = oldTrackSelections.get(i); TrackSelection oldSelection = oldTrackSelectorResult.selections.get(i);
TrackSelection newSelection = newTrackSelections.get(i);
if (oldSelection == null) { if (oldSelection == null) {
// The renderer has no current stream and will be enabled when we play the next period. // The renderer has no current stream and will be enabled when we play the next period.
} else if (initialDiscontinuity) { } else if (initialDiscontinuity) {
...@@ -1166,9 +1164,12 @@ import java.io.IOException; ...@@ -1166,9 +1164,12 @@ import java.io.IOException;
// be disabled and re-enabled when it starts playing the next period. // be disabled and re-enabled when it starts playing the next period.
renderer.setCurrentStreamFinal(); renderer.setCurrentStreamFinal();
} else if (!renderer.isCurrentStreamFinal()) { } else if (!renderer.isCurrentStreamFinal()) {
if (newSelection != null) { TrackSelection newSelection = newTrackSelectorResult.selections.get(i);
// Replace the renderer's SampleStream so the transition to playing the next period RendererConfiguration oldConfig = oldTrackSelectorResult.rendererConfigurations[i];
// can be seamless. RendererConfiguration newConfig = newTrackSelectorResult.rendererConfigurations[i];
if (newSelection != null && newConfig.equals(oldConfig)) {
// Replace the renderer's SampleStream so the transition to playing the next period can
// be seamless.
Format[] formats = new Format[newSelection.length()]; Format[] formats = new Format[newSelection.length()];
for (int j = 0; j < formats.length; j++) { for (int j = 0; j < formats.length; j++) {
formats[j] = newSelection.getFormat(j); formats[j] = newSelection.getFormat(j);
...@@ -1176,8 +1177,9 @@ import java.io.IOException; ...@@ -1176,8 +1177,9 @@ import java.io.IOException;
renderer.replaceStream(formats, readingPeriodHolder.sampleStreams[i], renderer.replaceStream(formats, readingPeriodHolder.sampleStreams[i],
readingPeriodHolder.getRendererOffset()); readingPeriodHolder.getRendererOffset());
} else { } else {
// The renderer will be disabled when transitioning to playing the next period. Mark the // The renderer will be disabled when transitioning to playing the next period, either
// SampleStream as final to play out any remaining data. // because there's no new selection or because a configuration change is required. Mark
// the SampleStream as final to play out any remaining data.
renderer.setCurrentStreamFinal(); renderer.setCurrentStreamFinal();
} }
} }
...@@ -1354,6 +1356,8 @@ import java.io.IOException; ...@@ -1354,6 +1356,8 @@ import java.io.IOException;
if (newSelection != null) { if (newSelection != null) {
enabledRenderers[enabledRendererCount++] = renderer; enabledRenderers[enabledRendererCount++] = renderer;
if (renderer.getState() == Renderer.STATE_DISABLED) { if (renderer.getState() == Renderer.STATE_DISABLED) {
RendererConfiguration rendererConfiguration =
playingPeriodHolder.trackSelectorResult.rendererConfigurations[i];
// The renderer needs enabling with its new track selection. // The renderer needs enabling with its new track selection.
boolean playing = playWhenReady && state == ExoPlayer.STATE_READY; boolean playing = playWhenReady && state == ExoPlayer.STATE_READY;
// Consider as joining only if the renderer was previously disabled. // Consider as joining only if the renderer was previously disabled.
...@@ -1364,8 +1368,8 @@ import java.io.IOException; ...@@ -1364,8 +1368,8 @@ import java.io.IOException;
formats[j] = newSelection.getFormat(j); formats[j] = newSelection.getFormat(j);
} }
// Enable the renderer. // Enable the renderer.
renderer.enable(formats, playingPeriodHolder.sampleStreams[i], rendererPositionUs, renderer.enable(rendererConfiguration, formats, playingPeriodHolder.sampleStreams[i],
joining, playingPeriodHolder.getRendererOffset()); rendererPositionUs, joining, playingPeriodHolder.getRendererOffset());
MediaClock mediaClock = renderer.getMediaClock(); MediaClock mediaClock = renderer.getMediaClock();
if (mediaClock != null) { if (mediaClock != null) {
if (rendererMediaClock != null) { if (rendererMediaClock != null) {
...@@ -1410,7 +1414,7 @@ import java.io.IOException; ...@@ -1410,7 +1414,7 @@ import java.io.IOException;
private final LoadControl loadControl; private final LoadControl loadControl;
private final MediaSource mediaSource; private final MediaSource mediaSource;
private TrackSelectionArray periodTrackSelections; private TrackSelectorResult periodTrackSelectorResult;
public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities, public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
long rendererPositionOffsetUs, TrackSelector trackSelector, LoadControl loadControl, long rendererPositionOffsetUs, TrackSelector trackSelector, LoadControl loadControl,
...@@ -1463,8 +1467,7 @@ import java.io.IOException; ...@@ -1463,8 +1467,7 @@ import java.io.IOException;
public boolean selectTracks() throws ExoPlaybackException { public boolean selectTracks() throws ExoPlaybackException {
TrackSelectorResult selectorResult = trackSelector.selectTracks(rendererCapabilities, TrackSelectorResult selectorResult = trackSelector.selectTracks(rendererCapabilities,
mediaPeriod.getTrackGroups()); mediaPeriod.getTrackGroups());
TrackSelectionArray newTrackSelections = selectorResult.selections; if (selectorResult.isEquivalent(periodTrackSelectorResult)) {
if (newTrackSelections.equals(periodTrackSelections)) {
return false; return false;
} }
trackSelectorResult = selectorResult; trackSelectorResult = selectorResult;
...@@ -1481,14 +1484,13 @@ import java.io.IOException; ...@@ -1481,14 +1484,13 @@ import java.io.IOException;
TrackSelectionArray trackSelections = trackSelectorResult.selections; TrackSelectionArray trackSelections = trackSelectorResult.selections;
for (int i = 0; i < trackSelections.length; i++) { for (int i = 0; i < trackSelections.length; i++) {
mayRetainStreamFlags[i] = !forceRecreateStreams mayRetainStreamFlags[i] = !forceRecreateStreams
&& Util.areEqual(periodTrackSelections == null ? null : periodTrackSelections.get(i), && trackSelectorResult.isEquivalent(periodTrackSelectorResult, i);
trackSelections.get(i));
} }
// Disable streams on the period and get new streams for updated/newly-enabled tracks. // Disable streams on the period and get new streams for updated/newly-enabled tracks.
positionUs = mediaPeriod.selectTracks(trackSelections.getAll(), mayRetainStreamFlags, positionUs = mediaPeriod.selectTracks(trackSelections.getAll(), mayRetainStreamFlags,
sampleStreams, streamResetFlags, positionUs); sampleStreams, streamResetFlags, positionUs);
periodTrackSelections = trackSelections; periodTrackSelectorResult = trackSelectorResult;
// Update whether we have enabled tracks and sanity check the expected streams are non-null. // Update whether we have enabled tracks and sanity check the expected streams are non-null.
hasEnabledTracks = false; hasEnabledTracks = false;
......
...@@ -183,10 +183,8 @@ public final class Format implements Parcelable { ...@@ -183,10 +183,8 @@ public final class Format implements Parcelable {
*/ */
public final int accessibilityChannel; public final int accessibilityChannel;
// Lazily initialized hashcode and framework media format. // Lazily initialized hashcode.
private int hashCode; private int hashCode;
private MediaFormat frameworkMediaFormat;
// Video. // Video.
...@@ -495,25 +493,22 @@ public final class Format implements Parcelable { ...@@ -495,25 +493,22 @@ public final class Format implements Parcelable {
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
@TargetApi(16) @TargetApi(16)
public final MediaFormat getFrameworkMediaFormatV16() { public final MediaFormat getFrameworkMediaFormatV16() {
if (frameworkMediaFormat == null) { MediaFormat format = new MediaFormat();
MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, sampleMimeType);
format.setString(MediaFormat.KEY_MIME, sampleMimeType); maybeSetStringV16(format, MediaFormat.KEY_LANGUAGE, language);
maybeSetStringV16(format, MediaFormat.KEY_LANGUAGE, language); maybeSetIntegerV16(format, MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
maybeSetIntegerV16(format, MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize); maybeSetIntegerV16(format, MediaFormat.KEY_WIDTH, width);
maybeSetIntegerV16(format, MediaFormat.KEY_WIDTH, width); maybeSetIntegerV16(format, MediaFormat.KEY_HEIGHT, height);
maybeSetIntegerV16(format, MediaFormat.KEY_HEIGHT, height); maybeSetFloatV16(format, MediaFormat.KEY_FRAME_RATE, frameRate);
maybeSetFloatV16(format, MediaFormat.KEY_FRAME_RATE, frameRate); maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees);
maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees); maybeSetIntegerV16(format, MediaFormat.KEY_CHANNEL_COUNT, channelCount);
maybeSetIntegerV16(format, MediaFormat.KEY_CHANNEL_COUNT, channelCount); maybeSetIntegerV16(format, MediaFormat.KEY_SAMPLE_RATE, sampleRate);
maybeSetIntegerV16(format, MediaFormat.KEY_SAMPLE_RATE, sampleRate); maybeSetIntegerV16(format, "encoder-delay", encoderDelay);
maybeSetIntegerV16(format, "encoder-delay", encoderDelay); maybeSetIntegerV16(format, "encoder-padding", encoderPadding);
maybeSetIntegerV16(format, "encoder-padding", encoderPadding); for (int i = 0; i < initializationData.size(); i++) {
for (int i = 0; i < initializationData.size(); i++) { format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i)));
format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i)));
}
frameworkMediaFormat = format;
} }
return frameworkMediaFormat; return format;
} }
@Override @Override
......
...@@ -92,6 +92,7 @@ public interface Renderer extends ExoPlayerComponent { ...@@ -92,6 +92,7 @@ public interface Renderer extends ExoPlayerComponent {
* This method may be called when the renderer is in the following states: * This method may be called when the renderer is in the following states:
* {@link #STATE_DISABLED}. * {@link #STATE_DISABLED}.
* *
* @param configuration The renderer configuration.
* @param formats The enabled formats. * @param formats The enabled formats.
* @param stream The {@link SampleStream} from which the renderer should consume. * @param stream The {@link SampleStream} from which the renderer should consume.
* @param positionUs The player's current position. * @param positionUs The player's current position.
...@@ -100,8 +101,8 @@ public interface Renderer extends ExoPlayerComponent { ...@@ -100,8 +101,8 @@ public interface Renderer extends ExoPlayerComponent {
* before they are rendered. * before they are rendered.
* @throws ExoPlaybackException If an error occurs. * @throws ExoPlaybackException If an error occurs.
*/ */
void enable(Format[] formats, SampleStream stream, long positionUs, boolean joining, void enable(RendererConfiguration configuration, Format[] formats, SampleStream stream,
long offsetUs) throws ExoPlaybackException; long positionUs, boolean joining, long offsetUs) throws ExoPlaybackException;
/** /**
* Starts the renderer, meaning that calls to {@link #render(long, long)} will cause media to be * Starts the renderer, meaning that calls to {@link #render(long, long)} will cause media to be
......
/*
* 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;
/**
* The configuration of a {@link Renderer}.
*/
public final class RendererConfiguration {
/**
* The default configuration.
*/
public static final RendererConfiguration DEFAULT =
new RendererConfiguration(C.AUDIO_SESSION_ID_UNSET);
/**
* The audio session id to use for tunneling, or {@link C#AUDIO_SESSION_ID_UNSET} if tunneling
* should not be enabled.
*/
public final int tunnelingAudioSessionId;
/**
* @param tunnelingAudioSessionId The audio session id to use for tunneling, or
* {@link C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled.
*/
public RendererConfiguration(int tunnelingAudioSessionId) {
this.tunnelingAudioSessionId = tunnelingAudioSessionId;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
RendererConfiguration other = (RendererConfiguration) obj;
return tunnelingAudioSessionId == other.tunnelingAudioSessionId;
}
@Override
public int hashCode() {
return tunnelingAudioSessionId;
}
}
...@@ -259,8 +259,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -259,8 +259,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining) throws ExoPlaybackException {
super.onEnabled(joining); super.onEnabled(joining);
eventDispatcher.enabled(decoderCounters); eventDispatcher.enabled(decoderCounters);
// TODO: Allow this to be set. int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId;
int tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET;
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
audioTrack.enableTunnelingV21(tunnelingAudioSessionId); audioTrack.enableTunnelingV21(tunnelingAudioSessionId);
} else { } else {
......
...@@ -407,8 +407,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -407,8 +407,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining) throws ExoPlaybackException {
decoderCounters = new DecoderCounters(); decoderCounters = new DecoderCounters();
eventDispatcher.enabled(decoderCounters); eventDispatcher.enabled(decoderCounters);
// TODO: Allow this to be set. int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId;
int tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET;
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
audioTrack.enableTunnelingV21(tunnelingAudioSessionId); audioTrack.enableTunnelingV21(tunnelingAudioSessionId);
} else { } else {
......
...@@ -15,11 +15,13 @@ ...@@ -15,11 +15,13 @@
*/ */
package com.google.android.exoplayer2.trackselection; package com.google.android.exoplayer2.trackselection;
import android.content.Context;
import android.util.SparseArray; import android.util.SparseArray;
import android.util.SparseBooleanArray; import android.util.SparseBooleanArray;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.RendererConfiguration;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
...@@ -81,12 +83,14 @@ public abstract class MappingTrackSelector extends TrackSelector { ...@@ -81,12 +83,14 @@ public abstract class MappingTrackSelector extends TrackSelector {
private final SparseArray<Map<TrackGroupArray, SelectionOverride>> selectionOverrides; private final SparseArray<Map<TrackGroupArray, SelectionOverride>> selectionOverrides;
private final SparseBooleanArray rendererDisabledFlags; private final SparseBooleanArray rendererDisabledFlags;
private int tunnelingAudioSessionId;
private MappedTrackInfo currentMappedTrackInfo; private MappedTrackInfo currentMappedTrackInfo;
public MappingTrackSelector() { public MappingTrackSelector() {
selectionOverrides = new SparseArray<>(); selectionOverrides = new SparseArray<>();
rendererDisabledFlags = new SparseBooleanArray(); rendererDisabledFlags = new SparseBooleanArray();
tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET;
} }
/** /**
...@@ -223,6 +227,23 @@ public abstract class MappingTrackSelector extends TrackSelector { ...@@ -223,6 +227,23 @@ public abstract class MappingTrackSelector extends TrackSelector {
invalidate(); invalidate();
} }
/**
* Enables or disables tunneling. To enable tunneling, pass an audio session id to use when in
* tunneling mode. Session ids can be generated using
* {@link C#generateAudioSessionIdV21(Context)}. To disable tunneling pass
* {@link C#AUDIO_SESSION_ID_UNSET}. Tunneling will only be activated if it's both enabled and
* supported by the audio and video renderers for the selected tracks.
*
* @param tunnelingAudioSessionId The audio session id to use when tunneling, or
* {@link C#AUDIO_SESSION_ID_UNSET} to disable tunneling.
*/
public void setTunnelingAudioSessionId(int tunnelingAudioSessionId) {
if (this.tunnelingAudioSessionId != tunnelingAudioSessionId) {
this.tunnelingAudioSessionId = tunnelingAudioSessionId;
invalidate();
}
}
// TrackSelector implementation. // TrackSelector implementation.
@Override @Override
...@@ -295,8 +316,20 @@ public abstract class MappingTrackSelector extends TrackSelector { ...@@ -295,8 +316,20 @@ public abstract class MappingTrackSelector extends TrackSelector {
MappedTrackInfo mappedTrackInfo = new MappedTrackInfo(rendererTrackTypes, MappedTrackInfo mappedTrackInfo = new MappedTrackInfo(rendererTrackTypes,
rendererTrackGroupArrays, mixedMimeTypeAdaptationSupport, rendererFormatSupports, rendererTrackGroupArrays, mixedMimeTypeAdaptationSupport, rendererFormatSupports,
unassociatedTrackGroupArray); unassociatedTrackGroupArray);
// Initialize the renderer configurations to the default configuration for all renderers with
// selections, and null otherwise.
RendererConfiguration[] rendererConfigurations =
new RendererConfiguration[rendererCapabilities.length];
for (int i = 0; i < rendererCapabilities.length; i++) {
rendererConfigurations[i] = trackSelections[i] != null ? RendererConfiguration.DEFAULT : null;
}
// Configure audio and video renderers to use tunneling if appropriate.
maybeConfigureRenderersForTunneling(rendererCapabilities, rendererTrackGroupArrays,
rendererFormatSupports, rendererConfigurations, trackSelections, tunnelingAudioSessionId);
return new TrackSelectorResult(trackGroups, new TrackSelectionArray(trackSelections), return new TrackSelectorResult(trackGroups, new TrackSelectionArray(trackSelections),
mappedTrackInfo); mappedTrackInfo, rendererConfigurations);
} }
@Override @Override
...@@ -400,6 +433,94 @@ public abstract class MappingTrackSelector extends TrackSelector { ...@@ -400,6 +433,94 @@ public abstract class MappingTrackSelector extends TrackSelector {
} }
/** /**
* Determines whether tunneling should be enabled, replacing {@link RendererConfiguration}s in
* {@code rendererConfigurations} with configurations that enable tunneling on the appropriate
* renderers if so.
*
* @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which
* {@link TrackSelection}s are to be generated.
* @param rendererTrackGroupArrays An array of {@link TrackGroupArray}s where each entry
* corresponds to the renderer of equal index in {@code renderers}.
* @param rendererFormatSupports Maps every available track to a specific level of support as
* defined by the renderer {@code FORMAT_*} constants.
* @param rendererConfigurations The renderer configurations. Configurations may be replaced with
* ones that enable tunneling as a result of this call.
* @param trackSelections The renderer track selections.
* @param tunnelingAudioSessionId The audio session id to use when tunneling, or
* {@link C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled.
*/
private static void maybeConfigureRenderersForTunneling(
RendererCapabilities[] rendererCapabilities, TrackGroupArray[] rendererTrackGroupArrays,
int[][][] rendererFormatSupports, RendererConfiguration[] rendererConfigurations,
TrackSelection[] trackSelections, int tunnelingAudioSessionId) {
if (tunnelingAudioSessionId == C.AUDIO_SESSION_ID_UNSET) {
return;
}
// Check whether we can enable tunneling. To enable tunneling we require exactly one audio and
// one video renderer to support tunneling and have a selection.
int tunnelingAudioRendererIndex = -1;
int tunnelingVideoRendererIndex = -1;
boolean enableTunneling = true;
for (int i = 0; i < rendererCapabilities.length; i++) {
int rendererType = rendererCapabilities[i].getTrackType();
TrackSelection trackSelection = trackSelections[i];
if ((rendererType == C.TRACK_TYPE_AUDIO || rendererType == C.TRACK_TYPE_VIDEO)
&& trackSelection != null) {
if (rendererSupportsTunneling(rendererFormatSupports[i], rendererTrackGroupArrays[i],
trackSelection)) {
if (rendererType == C.TRACK_TYPE_AUDIO) {
if (tunnelingAudioRendererIndex != -1) {
enableTunneling = false;
break;
} else {
tunnelingAudioRendererIndex = i;
}
} else {
if (tunnelingVideoRendererIndex != -1) {
enableTunneling = false;
break;
} else {
tunnelingVideoRendererIndex = i;
}
}
}
}
}
enableTunneling &= tunnelingAudioRendererIndex != -1 && tunnelingVideoRendererIndex != -1;
if (enableTunneling) {
RendererConfiguration tunnelingRendererConfiguration =
new RendererConfiguration(tunnelingAudioSessionId);
rendererConfigurations[tunnelingAudioRendererIndex] = tunnelingRendererConfiguration;
rendererConfigurations[tunnelingVideoRendererIndex] = tunnelingRendererConfiguration;
}
}
/**
* Returns whether a renderer supports tunneling for a {@link TrackSelection}.
*
* @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each
* track, indexed by group index and track index (in that order).
* @param trackGroups The {@link TrackGroupArray}s for the renderer.
* @param selection The track selection.
* @return Whether the renderer supports tunneling for the {@link TrackSelection}.
*/
private static boolean rendererSupportsTunneling(int[][] formatSupport,
TrackGroupArray trackGroups, TrackSelection selection) {
if (selection == null) {
return false;
}
int trackGroupIndex = trackGroups.indexOf(selection.getTrackGroup());
for (int i = 0; i < selection.length(); i++) {
int trackFormatSupport = formatSupport[trackGroupIndex][selection.getIndexInTrackGroup(i)];
if ((trackFormatSupport & RendererCapabilities.TUNNELING_SUPPORT_MASK)
!= RendererCapabilities.TUNNELING_SUPPORTED) {
return false;
}
}
return true;
}
/**
* Provides track information for each renderer. * Provides track information for each renderer.
*/ */
public static final class MappedTrackInfo { public static final class MappedTrackInfo {
......
...@@ -15,7 +15,9 @@ ...@@ -15,7 +15,9 @@
*/ */
package com.google.android.exoplayer2.trackselection; package com.google.android.exoplayer2.trackselection;
import com.google.android.exoplayer2.RendererConfiguration;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.util.Util;
/** /**
* The result of a {@link TrackSelector} operation. * The result of a {@link TrackSelector} operation.
...@@ -35,17 +37,57 @@ public final class TrackSelectorResult { ...@@ -35,17 +37,57 @@ public final class TrackSelectorResult {
* should the selections be activated. * should the selections be activated.
*/ */
public final Object info; public final Object info;
/**
* A {@link RendererConfiguration} for each renderer, to be used with the selections.
*/
public final RendererConfiguration[] rendererConfigurations;
/** /**
* @param groups The groups provided to the {@link TrackSelector}. * @param groups The groups provided to the {@link TrackSelector}.
* @param selections A {@link TrackSelectionArray} containing the selection for each renderer. * @param selections A {@link TrackSelectionArray} containing the selection for each renderer.
* @param info An opaque object that will be returned to * @param info An opaque object that will be returned to
* {@link TrackSelector#onSelectionActivated(Object)} should the selections be activated. * {@link TrackSelector#onSelectionActivated(Object)} should the selections be activated.
* @param rendererConfigurations A {@link RendererConfiguration} for each renderer, to be used
* with the selections.
*/ */
public TrackSelectorResult(TrackGroupArray groups, TrackSelectionArray selections, Object info) { public TrackSelectorResult(TrackGroupArray groups, TrackSelectionArray selections, Object info,
RendererConfiguration[] rendererConfigurations) {
this.groups = groups; this.groups = groups;
this.selections = selections; this.selections = selections;
this.info = info; this.info = info;
this.rendererConfigurations = rendererConfigurations;
}
/**
* Returns whether this result is equivalent to {@code other} for all renderers.
*
* @param other The other {@link TrackSelectorResult}. May be null.
* @return Whether this result is equivalent to {@code other} for all renderers.
*/
public boolean isEquivalent(TrackSelectorResult other) {
for (int i = 0; i < selections.length; i++) {
if (!isEquivalent(other, i)) {
return false;
}
}
return true;
}
/**
* Returns whether this result is equivalent to {@code other} for the renderer at the given index.
* The results are equivalent if they have equal track selections and configurations for the
* renderer.
*
* @param other The other {@link TrackSelectorResult}. May be null.
* @param index The renderer index to check for equivalence.
* @return Whether this result is equivalent to {@code other} for all renderers.
*/
public boolean isEquivalent(TrackSelectorResult other, int index) {
if (other == null) {
return selections.get(index) == null && rendererConfigurations[index] == null;
}
return Util.areEqual(selections.get(index), other.selections.get(index))
&& Util.areEqual(rendererConfigurations[index], other.rendererConfigurations[index]);
} }
} }
...@@ -210,8 +210,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -210,8 +210,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@Override @Override
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining) throws ExoPlaybackException {
super.onEnabled(joining); super.onEnabled(joining);
// TODO: Allow this to be set. tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId;
tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET;
tunneling = tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET; tunneling = tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET;
eventDispatcher.enabled(decoderCounters); eventDispatcher.enabled(decoderCounters);
frameReleaseTimeHelper.enable(); frameReleaseTimeHelper.enable();
...@@ -583,12 +582,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -583,12 +582,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
// Configure tunneling if enabled. // Configure tunneling if enabled.
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
frameworkMediaFormat.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true); configureTunnelingV21(frameworkMediaFormat, tunnelingAudioSessionId);
frameworkMediaFormat.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, tunnelingAudioSessionId);
} }
return frameworkMediaFormat; return frameworkMediaFormat;
} }
@TargetApi(21)
private static void configureTunnelingV21(MediaFormat mediaFormat, int tunnelingAudioSessionId) {
mediaFormat.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true);
mediaFormat.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, tunnelingAudioSessionId);
}
/** /**
* Returns {@link CodecMaxValues} suitable for configuring a codec for {@code format} in a way * Returns {@link CodecMaxValues} suitable for configuring a codec for {@code format} in a way
* that will allow possible adaptation to other compatible formats in {@code streamFormats}. * that will allow possible adaptation to other compatible formats in {@code streamFormats}.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment