Enable passthrough based on the input MIME type.

This commit is contained in:
Oliver Woodman 2015-05-08 17:04:21 +01:00
parent a1083d360a
commit 3360f5eda5
7 changed files with 84 additions and 56 deletions

View File

@ -59,7 +59,6 @@ import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.media.AudioFormat;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.UnsupportedSchemeException; import android.media.UnsupportedSchemeException;
import android.os.Handler; import android.os.Handler;
@ -249,7 +248,6 @@ public class DashRendererBuilder implements RendererBuilder,
// Build the audio chunk sources. // Build the audio chunk sources.
List<ChunkSource> audioChunkSourceList = new ArrayList<ChunkSource>(); List<ChunkSource> audioChunkSourceList = new ArrayList<ChunkSource>();
List<String> audioTrackNameList = new ArrayList<String>(); List<String> audioTrackNameList = new ArrayList<String>();
int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
if (audioAdaptationSet != null) { if (audioAdaptationSet != null) {
DataSource audioDataSource = new DefaultUriDataSource(userAgent, bandwidthMeter); DataSource audioDataSource = new DefaultUriDataSource(userAgent, bandwidthMeter);
FormatEvaluator audioEvaluator = new FormatEvaluator.FixedEvaluator(); FormatEvaluator audioEvaluator = new FormatEvaluator.FixedEvaluator();
@ -275,7 +273,6 @@ public class DashRendererBuilder implements RendererBuilder,
continue; continue;
} }
audioEncoding = encoding;
for (int j = audioRepresentations.size() - 1; j >= 0; j--) { for (int j = audioRepresentations.size() - 1; j >= 0; j--) {
if (!audioRepresentations.get(j).format.codecs.equals(codec)) { if (!audioRepresentations.get(j).format.codecs.equals(codec)) {
audioTrackNameList.remove(j); audioTrackNameList.remove(j);
@ -303,7 +300,7 @@ public class DashRendererBuilder implements RendererBuilder,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player, AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
DemoPlayer.TYPE_AUDIO); DemoPlayer.TYPE_AUDIO);
audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource, drmSessionManager, true, audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource, drmSessionManager, true,
mainHandler, player, audioEncoding); mainHandler, player);
} }
// Build the text chunk sources. // Build the text chunk sources.

View File

@ -21,9 +21,7 @@ import com.google.android.exoplayer.drm.DrmSessionManager;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.media.AudioFormat;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaFormat;
import android.media.audiofx.Virtualizer; import android.media.audiofx.Virtualizer;
import android.os.Handler; import android.os.Handler;
@ -71,7 +69,6 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
private final EventListener eventListener; private final EventListener eventListener;
private final AudioTrack audioTrack; private final AudioTrack audioTrack;
private final int encoding;
private int audioSessionId; private int audioSessionId;
private long currentPositionUs; private long currentPositionUs;
@ -124,50 +121,27 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
*/ */
public MediaCodecAudioTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager, public MediaCodecAudioTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager,
boolean playClearSamplesWithoutKeys, Handler eventHandler, EventListener eventListener) { boolean playClearSamplesWithoutKeys, Handler eventHandler, EventListener eventListener) {
this(source, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, eventListener,
AudioFormat.ENCODING_PCM_16BIT);
}
/**
* @param source The upstream source from which the renderer obtains samples.
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
* content is not required.
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
* For example a media file may start with a short clear region so as to allow playback to
* begin in parallel with key acquisision. This parameter specifies whether the renderer is
* permitted to play clear regions of encrypted media files before {@code drmSessionManager}
* has obtained the keys necessary to decrypt encrypted regions of the media.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param encoding One of the {@code AudioFormat.ENCODING_*} constants specifying the audio
* encoding.
*/
public MediaCodecAudioTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager,
boolean playClearSamplesWithoutKeys, Handler eventHandler, EventListener eventListener,
int encoding) {
super(source, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, eventListener); super(source, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, eventListener);
this.eventListener = eventListener; this.eventListener = eventListener;
this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET; this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
this.audioTrack = new AudioTrack(); this.audioTrack = new AudioTrack();
this.encoding = encoding;
} }
@Override @Override
protected DecoderInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder) protected DecoderInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder)
throws DecoderQueryException { throws DecoderQueryException {
if (encoding == AudioFormat.ENCODING_AC3 || encoding == AudioFormat.ENCODING_E_AC3) { if (MimeTypes.isPassthroughAudio(mimeType)) {
return new DecoderInfo(RAW_DECODER_NAME, true); return new DecoderInfo(RAW_DECODER_NAME, true);
} }
return super.getDecoderInfo(mimeType, requiresSecureDecoder); return super.getDecoderInfo(mimeType, requiresSecureDecoder);
} }
@Override @Override
protected void configureCodec(MediaCodec codec, String codecName, MediaFormat format, protected void configureCodec(MediaCodec codec, String codecName,
android.media.MediaCrypto crypto) { android.media.MediaFormat format, android.media.MediaCrypto crypto) {
if (RAW_DECODER_NAME.equals(codecName)) { if (RAW_DECODER_NAME.equals(codecName)) {
// Override the MIME type used to configure the codec if we are using a passthrough decoder. // Override the MIME type used to configure the codec if we are using a passthrough decoder.
String mimeType = format.getString(MediaFormat.KEY_MIME); String mimeType = format.getString(android.media.MediaFormat.KEY_MIME);
format.setString(android.media.MediaFormat.KEY_MIME, MimeTypes.AUDIO_RAW); format.setString(android.media.MediaFormat.KEY_MIME, MimeTypes.AUDIO_RAW);
codec.configure(format, null, crypto, 0); codec.configure(format, null, crypto, 0);
format.setString(android.media.MediaFormat.KEY_MIME, mimeType); format.setString(android.media.MediaFormat.KEY_MIME, mimeType);
@ -193,8 +167,13 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
} }
@Override @Override
protected void onOutputFormatChanged(MediaFormat format) { protected void onOutputFormatChanged(MediaFormat inputFormat,
audioTrack.reconfigure(format, encoding, 0); android.media.MediaFormat outputFormat) {
if (MimeTypes.isPassthroughAudio(inputFormat.mimeType)) {
audioTrack.reconfigure(inputFormat.getFrameworkMediaFormatV16());
} else {
audioTrack.reconfigure(outputFormat);
}
} }
/** /**

View File

@ -742,9 +742,11 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
* <p> * <p>
* The default implementation is a no-op. * The default implementation is a no-op.
* *
* @param format The new output format. * @param inputFormat The format of media input to the codec.
* @param outputFormat The new output format.
*/ */
protected void onOutputFormatChanged(android.media.MediaFormat format) { protected void onOutputFormatChanged(MediaFormat inputFormat,
android.media.MediaFormat outputFormat) {
// Do nothing. // Do nothing.
} }
@ -818,7 +820,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
} }
if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
onOutputFormatChanged(codec.getOutputFormat()); onOutputFormatChanged(format, codec.getOutputFormat());
codecCounters.outputFormatChangedCount++; codecCounters.outputFormatChangedCount++;
return true; return true;
} else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { } else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {

View File

@ -381,15 +381,17 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
} }
@Override @Override
protected void onOutputFormatChanged(android.media.MediaFormat format) { protected void onOutputFormatChanged(MediaFormat inputFormat,
boolean hasCrop = format.containsKey(KEY_CROP_RIGHT) && format.containsKey(KEY_CROP_LEFT) android.media.MediaFormat outputFormat) {
&& format.containsKey(KEY_CROP_BOTTOM) && format.containsKey(KEY_CROP_TOP); boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT)
&& outputFormat.containsKey(KEY_CROP_LEFT) && outputFormat.containsKey(KEY_CROP_BOTTOM)
&& outputFormat.containsKey(KEY_CROP_TOP);
currentWidth = hasCrop currentWidth = hasCrop
? format.getInteger(KEY_CROP_RIGHT) - format.getInteger(KEY_CROP_LEFT) + 1 ? outputFormat.getInteger(KEY_CROP_RIGHT) - outputFormat.getInteger(KEY_CROP_LEFT) + 1
: format.getInteger(android.media.MediaFormat.KEY_WIDTH); : outputFormat.getInteger(android.media.MediaFormat.KEY_WIDTH);
currentHeight = hasCrop currentHeight = hasCrop
? format.getInteger(KEY_CROP_BOTTOM) - format.getInteger(KEY_CROP_TOP) + 1 ? outputFormat.getInteger(KEY_CROP_BOTTOM) - outputFormat.getInteger(KEY_CROP_TOP) + 1
: format.getInteger(android.media.MediaFormat.KEY_HEIGHT); : outputFormat.getInteger(android.media.MediaFormat.KEY_HEIGHT);
} }
@Override @Override

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer.audio;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.util.Ac3Util; import com.google.android.exoplayer.util.Ac3Util;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi; import android.annotation.TargetApi;
@ -315,24 +316,21 @@ public final class AudioTrack {
} }
/** /**
* Reconfigures the audio track to play back media in {@code format}. The encoding is assumed to * Reconfigures the audio track to play back media in {@code format}, inferring a buffer size from
* be {@link AudioFormat#ENCODING_PCM_16BIT}. * the format.
*/ */
public void reconfigure(MediaFormat format) { public void reconfigure(MediaFormat format) {
reconfigure(format, AudioFormat.ENCODING_PCM_16BIT, 0); reconfigure(format, 0);
} }
/** /**
* Reconfigures the audio track to play back media in {@code format}. Buffers passed to * Reconfigures the audio track to play back media in {@code format}.
* {@link #handleBuffer} must use the specified {@code encoding}, which should be a constant from
* {@link AudioFormat}.
* *
* @param format Specifies the channel count and sample rate to play back. * @param format Specifies the channel count and sample rate to play back.
* @param encoding The format in which audio is represented.
* @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to use a * @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to use a
* size inferred from the format. * size inferred from the format.
*/ */
public void reconfigure(MediaFormat format, int encoding, int specifiedBufferSize) { public void reconfigure(MediaFormat format, int specifiedBufferSize) {
int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
int channelConfig; int channelConfig;
switch (channelCount) { switch (channelCount) {
@ -353,8 +351,10 @@ public final class AudioTrack {
} }
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
String mimeType = format.getString(MediaFormat.KEY_MIME);
// TODO: Does channelConfig determine channelCount? // TODO: Does channelConfig determine channelCount?
int encoding = MimeTypes.getEncodingForMimeType(mimeType);
boolean isAc3 = encoding == C.ENCODING_AC3 || encoding == C.ENCODING_E_AC3; boolean isAc3 = encoding == C.ENCODING_AC3 || encoding == C.ENCODING_E_AC3;
if (isInitialized() && this.sampleRate == sampleRate && this.channelConfig == channelConfig if (isInitialized() && this.sampleRate == sampleRate && this.channelConfig == channelConfig
&& !this.isAc3 && !isAc3) { && !this.isAc3 && !isAc3) {

View File

@ -534,8 +534,18 @@ import java.util.List;
childPosition += childAtomSize; childPosition += childAtomSize;
} }
out.mediaFormat = MediaFormat.createAudioFormat( // Set the MIME type for ac-3/ec-3 atoms even if the dac3/dec3 child atom is missing.
MimeTypes.AUDIO_AAC, sampleSize, durationUs, channelCount, sampleRate, String mimeType;
if (atomType == Atom.TYPE_ac_3) {
mimeType = MimeTypes.AUDIO_AC3;
} else if (atomType == Atom.TYPE_ec_3) {
mimeType = MimeTypes.AUDIO_EC3;
} else {
mimeType = MimeTypes.AUDIO_AAC;
}
out.mediaFormat = MediaFormat.createAudioFormat(mimeType, sampleSize, durationUs, channelCount,
sampleRate,
initializationData == null ? null : Collections.singletonList(initializationData)); initializationData == null ? null : Collections.singletonList(initializationData));
} }

View File

@ -15,6 +15,11 @@
*/ */
package com.google.android.exoplayer.util; package com.google.android.exoplayer.util;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.audio.AudioCapabilities;
import android.media.AudioFormat;
/** /**
* Defines common MIME types and helper methods. * Defines common MIME types and helper methods.
*/ */
@ -119,4 +124,37 @@ public class MimeTypes {
return mimeType.equals(APPLICATION_TTML); return mimeType.equals(APPLICATION_TTML);
} }
/**
* Returns the output audio encoding that will result from processing input in {@code mimeType}.
* For non-passthrough audio formats, this is always {@link AudioFormat#ENCODING_PCM_16BIT}. For
* passthrough formats it will be one of {@link AudioFormat}'s other {@code ENCODING_*} constants.
* For non-audio formats, {@link AudioFormat#ENCODING_INVALID} will be returned.
*
* @param mimeType The MIME type of media that will be decoded (or passed through).
* @return The corresponding {@link AudioFormat} encoding.
*/
public static int getEncodingForMimeType(String mimeType) {
if (AUDIO_AC3.equals(mimeType)) {
return C.ENCODING_AC3;
}
if (AUDIO_EC3.equals(mimeType)) {
return C.ENCODING_E_AC3;
}
// All other audio formats will be decoded to 16-bit PCM.
return isAudio(mimeType) ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_INVALID;
}
/**
* Returns whether the specified {@code mimeType} represents audio that can be played via
* passthrough if the device supports it.
*
* @param mimeType The MIME type of input media.
* @return Whether the audio can be played via passthrough. If this method returns {@code true},
* it is still necessary to check the {@link AudioCapabilities} for device support.
*/
public static boolean isPassthroughAudio(String mimeType) {
return AUDIO_AC3.equals(mimeType) || AUDIO_EC3.equals(mimeType);
}
} }