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
This commit is contained in:
Oliver Woodman 2017-01-17 17:21:10 +00:00
parent c828d9b0bf
commit f1e3d3f244
10 changed files with 286 additions and 55 deletions

View file

@ -28,6 +28,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
private final int trackType;
private RendererConfiguration configuration;
private int index;
private int state;
private SampleStream stream;
@ -70,9 +71,11 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
}
@Override
public final void enable(Format[] formats, SampleStream stream, long positionUs, boolean joining,
long offsetUs) throws ExoPlaybackException {
public final void enable(RendererConfiguration configuration, Format[] formats,
SampleStream stream, long positionUs, boolean joining, long offsetUs)
throws ExoPlaybackException {
Assertions.checkState(state == STATE_DISABLED);
this.configuration = configuration;
state = STATE_ENABLED;
onEnabled(joining);
replaceStream(formats, stream, offsetUs);
@ -237,10 +240,15 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
// 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.
*
* @return The index of the renderer within the player.
*/
protected final int getIndex() {
return index;

View file

@ -35,7 +35,6 @@ import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.PriorityHandlerThread;
import com.google.android.exoplayer2.util.StandaloneMediaClock;
import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/**
@ -1149,16 +1148,15 @@ import java.io.IOException;
}
if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) {
TrackSelectionArray oldTrackSelections = readingPeriodHolder.trackSelectorResult.selections;
TrackSelectorResult oldTrackSelectorResult = readingPeriodHolder.trackSelectorResult;
readingPeriodHolder = readingPeriodHolder.next;
TrackSelectionArray newTrackSelections = readingPeriodHolder.trackSelectorResult.selections;
TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.trackSelectorResult;
boolean initialDiscontinuity =
readingPeriodHolder.mediaPeriod.readDiscontinuity() != C.TIME_UNSET;
for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i];
TrackSelection oldSelection = oldTrackSelections.get(i);
TrackSelection newSelection = newTrackSelections.get(i);
TrackSelection oldSelection = oldTrackSelectorResult.selections.get(i);
if (oldSelection == null) {
// The renderer has no current stream and will be enabled when we play the next period.
} else if (initialDiscontinuity) {
@ -1166,9 +1164,12 @@ import java.io.IOException;
// be disabled and re-enabled when it starts playing the next period.
renderer.setCurrentStreamFinal();
} else if (!renderer.isCurrentStreamFinal()) {
if (newSelection != null) {
// Replace the renderer's SampleStream so the transition to playing the next period
// can be seamless.
TrackSelection newSelection = newTrackSelectorResult.selections.get(i);
RendererConfiguration oldConfig = oldTrackSelectorResult.rendererConfigurations[i];
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()];
for (int j = 0; j < formats.length; j++) {
formats[j] = newSelection.getFormat(j);
@ -1176,8 +1177,9 @@ import java.io.IOException;
renderer.replaceStream(formats, readingPeriodHolder.sampleStreams[i],
readingPeriodHolder.getRendererOffset());
} else {
// The renderer will be disabled when transitioning to playing the next period. Mark the
// SampleStream as final to play out any remaining data.
// The renderer will be disabled when transitioning to playing the next period, either
// 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();
}
}
@ -1354,6 +1356,8 @@ import java.io.IOException;
if (newSelection != null) {
enabledRenderers[enabledRendererCount++] = renderer;
if (renderer.getState() == Renderer.STATE_DISABLED) {
RendererConfiguration rendererConfiguration =
playingPeriodHolder.trackSelectorResult.rendererConfigurations[i];
// The renderer needs enabling with its new track selection.
boolean playing = playWhenReady && state == ExoPlayer.STATE_READY;
// Consider as joining only if the renderer was previously disabled.
@ -1364,8 +1368,8 @@ import java.io.IOException;
formats[j] = newSelection.getFormat(j);
}
// Enable the renderer.
renderer.enable(formats, playingPeriodHolder.sampleStreams[i], rendererPositionUs,
joining, playingPeriodHolder.getRendererOffset());
renderer.enable(rendererConfiguration, formats, playingPeriodHolder.sampleStreams[i],
rendererPositionUs, joining, playingPeriodHolder.getRendererOffset());
MediaClock mediaClock = renderer.getMediaClock();
if (mediaClock != null) {
if (rendererMediaClock != null) {
@ -1410,7 +1414,7 @@ import java.io.IOException;
private final LoadControl loadControl;
private final MediaSource mediaSource;
private TrackSelectionArray periodTrackSelections;
private TrackSelectorResult periodTrackSelectorResult;
public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
long rendererPositionOffsetUs, TrackSelector trackSelector, LoadControl loadControl,
@ -1463,8 +1467,7 @@ import java.io.IOException;
public boolean selectTracks() throws ExoPlaybackException {
TrackSelectorResult selectorResult = trackSelector.selectTracks(rendererCapabilities,
mediaPeriod.getTrackGroups());
TrackSelectionArray newTrackSelections = selectorResult.selections;
if (newTrackSelections.equals(periodTrackSelections)) {
if (selectorResult.isEquivalent(periodTrackSelectorResult)) {
return false;
}
trackSelectorResult = selectorResult;
@ -1481,14 +1484,13 @@ import java.io.IOException;
TrackSelectionArray trackSelections = trackSelectorResult.selections;
for (int i = 0; i < trackSelections.length; i++) {
mayRetainStreamFlags[i] = !forceRecreateStreams
&& Util.areEqual(periodTrackSelections == null ? null : periodTrackSelections.get(i),
trackSelections.get(i));
&& trackSelectorResult.isEquivalent(periodTrackSelectorResult, i);
}
// Disable streams on the period and get new streams for updated/newly-enabled tracks.
positionUs = mediaPeriod.selectTracks(trackSelections.getAll(), mayRetainStreamFlags,
sampleStreams, streamResetFlags, positionUs);
periodTrackSelections = trackSelections;
periodTrackSelectorResult = trackSelectorResult;
// Update whether we have enabled tracks and sanity check the expected streams are non-null.
hasEnabledTracks = false;

View file

@ -183,10 +183,8 @@ public final class Format implements Parcelable {
*/
public final int accessibilityChannel;
// Lazily initialized hashcode and framework media format.
// Lazily initialized hashcode.
private int hashCode;
private MediaFormat frameworkMediaFormat;
// Video.
@ -495,25 +493,22 @@ public final class Format implements Parcelable {
@SuppressLint("InlinedApi")
@TargetApi(16)
public final MediaFormat getFrameworkMediaFormatV16() {
if (frameworkMediaFormat == null) {
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, sampleMimeType);
maybeSetStringV16(format, MediaFormat.KEY_LANGUAGE, language);
maybeSetIntegerV16(format, MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
maybeSetIntegerV16(format, MediaFormat.KEY_WIDTH, width);
maybeSetIntegerV16(format, MediaFormat.KEY_HEIGHT, height);
maybeSetFloatV16(format, MediaFormat.KEY_FRAME_RATE, frameRate);
maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees);
maybeSetIntegerV16(format, MediaFormat.KEY_CHANNEL_COUNT, channelCount);
maybeSetIntegerV16(format, MediaFormat.KEY_SAMPLE_RATE, sampleRate);
maybeSetIntegerV16(format, "encoder-delay", encoderDelay);
maybeSetIntegerV16(format, "encoder-padding", encoderPadding);
for (int i = 0; i < initializationData.size(); i++) {
format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i)));
}
frameworkMediaFormat = format;
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, sampleMimeType);
maybeSetStringV16(format, MediaFormat.KEY_LANGUAGE, language);
maybeSetIntegerV16(format, MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
maybeSetIntegerV16(format, MediaFormat.KEY_WIDTH, width);
maybeSetIntegerV16(format, MediaFormat.KEY_HEIGHT, height);
maybeSetFloatV16(format, MediaFormat.KEY_FRAME_RATE, frameRate);
maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees);
maybeSetIntegerV16(format, MediaFormat.KEY_CHANNEL_COUNT, channelCount);
maybeSetIntegerV16(format, MediaFormat.KEY_SAMPLE_RATE, sampleRate);
maybeSetIntegerV16(format, "encoder-delay", encoderDelay);
maybeSetIntegerV16(format, "encoder-padding", encoderPadding);
for (int i = 0; i < initializationData.size(); i++) {
format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i)));
}
return frameworkMediaFormat;
return format;
}
@Override

View file

@ -92,6 +92,7 @@ public interface Renderer extends ExoPlayerComponent {
* This method may be called when the renderer is in the following states:
* {@link #STATE_DISABLED}.
*
* @param configuration The renderer configuration.
* @param formats The enabled formats.
* @param stream The {@link SampleStream} from which the renderer should consume.
* @param positionUs The player's current position.
@ -100,8 +101,8 @@ public interface Renderer extends ExoPlayerComponent {
* before they are rendered.
* @throws ExoPlaybackException If an error occurs.
*/
void enable(Format[] formats, SampleStream stream, long positionUs, boolean joining,
long offsetUs) throws ExoPlaybackException;
void enable(RendererConfiguration configuration, Format[] formats, SampleStream stream,
long positionUs, boolean joining, long offsetUs) throws ExoPlaybackException;
/**
* Starts the renderer, meaning that calls to {@link #render(long, long)} will cause media to be

View file

@ -0,0 +1,60 @@
/*
* 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;
}
}

View file

@ -259,8 +259,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
protected void onEnabled(boolean joining) throws ExoPlaybackException {
super.onEnabled(joining);
eventDispatcher.enabled(decoderCounters);
// TODO: Allow this to be set.
int tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET;
int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId;
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
audioTrack.enableTunnelingV21(tunnelingAudioSessionId);
} else {

View file

@ -407,8 +407,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
protected void onEnabled(boolean joining) throws ExoPlaybackException {
decoderCounters = new DecoderCounters();
eventDispatcher.enabled(decoderCounters);
// TODO: Allow this to be set.
int tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET;
int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId;
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
audioTrack.enableTunnelingV21(tunnelingAudioSessionId);
} else {

View file

@ -15,11 +15,13 @@
*/
package com.google.android.exoplayer2.trackselection;
import android.content.Context;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
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.TrackGroupArray;
import com.google.android.exoplayer2.util.Util;
@ -81,12 +83,14 @@ public abstract class MappingTrackSelector extends TrackSelector {
private final SparseArray<Map<TrackGroupArray, SelectionOverride>> selectionOverrides;
private final SparseBooleanArray rendererDisabledFlags;
private int tunnelingAudioSessionId;
private MappedTrackInfo currentMappedTrackInfo;
public MappingTrackSelector() {
selectionOverrides = new SparseArray<>();
rendererDisabledFlags = new SparseBooleanArray();
tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET;
}
/**
@ -223,6 +227,23 @@ public abstract class MappingTrackSelector extends TrackSelector {
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.
@Override
@ -295,8 +316,20 @@ public abstract class MappingTrackSelector extends TrackSelector {
MappedTrackInfo mappedTrackInfo = new MappedTrackInfo(rendererTrackTypes,
rendererTrackGroupArrays, mixedMimeTypeAdaptationSupport, rendererFormatSupports,
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),
mappedTrackInfo);
mappedTrackInfo, rendererConfigurations);
}
@Override
@ -399,6 +432,94 @@ public abstract class MappingTrackSelector extends TrackSelector {
return mixedMimeTypeAdaptationSupport;
}
/**
* 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.
*/

View file

@ -15,7 +15,9 @@
*/
package com.google.android.exoplayer2.trackselection;
import com.google.android.exoplayer2.RendererConfiguration;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.util.Util;
/**
* The result of a {@link TrackSelector} operation.
@ -35,17 +37,57 @@ public final class TrackSelectorResult {
* should the selections be activated.
*/
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 selections A {@link TrackSelectionArray} containing the selection for each renderer.
* @param info An opaque object that will be returned to
* {@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.selections = selections;
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]);
}
}

View file

@ -210,8 +210,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@Override
protected void onEnabled(boolean joining) throws ExoPlaybackException {
super.onEnabled(joining);
// TODO: Allow this to be set.
tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET;
tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId;
tunneling = tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET;
eventDispatcher.enabled(decoderCounters);
frameReleaseTimeHelper.enable();
@ -583,12 +582,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
// Configure tunneling if enabled.
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
frameworkMediaFormat.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true);
frameworkMediaFormat.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, tunnelingAudioSessionId);
configureTunnelingV21(frameworkMediaFormat, tunnelingAudioSessionId);
}
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
* that will allow possible adaptation to other compatible formats in {@code streamFormats}.