Support 8-bit and 24-bit PCM with re-sampling.

This is a cleaned up version of the pull request below,
which will be merged with this change then applied on top.

https://github.com/google/ExoPlayer/pull/1490

Issue: #1246
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=121926682
This commit is contained in:
olly 2016-05-10 02:33:15 -07:00 committed by Oliver Woodman
parent d2c524d726
commit b61e7cdbc4
16 changed files with 284 additions and 110 deletions

View File

@ -105,8 +105,8 @@ public final class FlacExtractor implements Extractor {
}); });
Format mediaFormat = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_RAW, Format mediaFormat = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_RAW,
streamInfo.bitRate(), Format.NO_VALUE, streamInfo.channels, streamInfo.sampleRate, null, streamInfo.bitRate(), Format.NO_VALUE, streamInfo.channels, streamInfo.sampleRate,
null, null); C.ENCODING_PCM_16BIT, null, null, null);
trackOutput.format(mediaFormat); trackOutput.format(mediaFormat);
outputBuffer = new ParsableByteArray(streamInfo.maxDecodedFrameSize()); outputBuffer = new ParsableByteArray(streamInfo.maxDecodedFrameSize());

View File

@ -59,6 +59,26 @@ public final class C {
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR; public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR;
/**
* @see AudioFormat#ENCODING_INVALID
*/
public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID;
/**
* @see AudioFormat#ENCODING_PCM_8BIT
*/
public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
/**
* @see AudioFormat#ENCODING_PCM_16BIT
*/
public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
/**
* PCM encoding with 24 bits per sample.
*/
public static final int ENCODING_PCM_24BIT = 0x80000000;
/** /**
* @see AudioFormat#ENCODING_AC3 * @see AudioFormat#ENCODING_AC3
*/ */

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer; package com.google.android.exoplayer;
import com.google.android.exoplayer.drm.DrmInitData; import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@ -131,6 +132,12 @@ public final class Format {
* The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable. * The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable.
*/ */
public final int sampleRate; public final int sampleRate;
/**
* The encoding for PCM audio streams. If {@link #sampleMimeType} is {@link MimeTypes#AUDIO_RAW}
* then one of {@link C#ENCODING_PCM_8BIT}, {@link C#ENCODING_PCM_16BIT} and
* {@link C#ENCODING_PCM_24BIT}. Set to {@link #NO_VALUE} for other media types.
*/
public final int pcmEncoding;
/** /**
* The number of samples to trim from the start of the decoded audio stream. * The number of samples to trim from the start of the decoded audio stream.
*/ */
@ -167,7 +174,7 @@ public final class Format {
String sampleMimeType, int bitrate, int width, int height, float frameRate, String sampleMimeType, int bitrate, int width, int height, float frameRate,
List<byte[]> initializationData) { List<byte[]> initializationData) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, width, height, return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, width, height,
frameRate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, frameRate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null,
OFFSET_SAMPLE_RELATIVE, initializationData, null, false); OFFSET_SAMPLE_RELATIVE, initializationData, null, false);
} }
@ -182,8 +189,8 @@ public final class Format {
int maxInputSize, int width, int height, float frameRate, List<byte[]> initializationData, int maxInputSize, int width, int height, float frameRate, List<byte[]> initializationData,
int rotationDegrees, float pixelWidthHeightRatio, DrmInitData drmInitData) { int rotationDegrees, float pixelWidthHeightRatio, DrmInitData drmInitData) {
return new Format(id, null, sampleMimeType, bitrate, maxInputSize, width, height, frameRate, return new Format(id, null, sampleMimeType, bitrate, maxInputSize, width, height, frameRate,
rotationDegrees, pixelWidthHeightRatio, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, rotationDegrees, pixelWidthHeightRatio, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, false); null, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, false);
} }
// Audio. // Audio.
@ -192,23 +199,31 @@ public final class Format {
String sampleMimeType, int bitrate, int channelCount, int sampleRate, String sampleMimeType, int bitrate, int channelCount, int sampleRate,
List<byte[]> initializationData, String language) { List<byte[]> initializationData, String language) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, channelCount, sampleRate, NO_VALUE, NO_VALUE, language, NO_VALUE, NO_VALUE, NO_VALUE, channelCount, sampleRate, NO_VALUE, NO_VALUE, NO_VALUE,
OFFSET_SAMPLE_RELATIVE, initializationData, null, false); language, OFFSET_SAMPLE_RELATIVE, initializationData, null, false);
} }
public static Format createAudioSampleFormat(String id, String sampleMimeType, int bitrate, public static Format createAudioSampleFormat(String id, String sampleMimeType, int bitrate,
int maxInputSize, int channelCount, int sampleRate, List<byte[]> initializationData, int maxInputSize, int channelCount, int sampleRate, List<byte[]> initializationData,
String language, DrmInitData drmInitData) { DrmInitData drmInitData, String language) {
return createAudioSampleFormat(id, sampleMimeType, bitrate, maxInputSize, channelCount, return createAudioSampleFormat(id, sampleMimeType, bitrate, maxInputSize, channelCount,
sampleRate, NO_VALUE, NO_VALUE, initializationData, language, drmInitData); sampleRate, NO_VALUE, initializationData, drmInitData, language);
} }
public static Format createAudioSampleFormat(String id, String sampleMimeType, int bitrate, public static Format createAudioSampleFormat(String id, String sampleMimeType, int bitrate,
int maxInputSize, int channelCount, int sampleRate, int encoderDelay, int encoderPadding, int maxInputSize, int channelCount, int sampleRate, int pcmEncoding,
List<byte[]> initializationData, String language, DrmInitData drmInitData) { List<byte[]> initializationData, DrmInitData drmInitData, String language) {
return createAudioSampleFormat(id, sampleMimeType, bitrate, maxInputSize, channelCount,
sampleRate, pcmEncoding, NO_VALUE, NO_VALUE, initializationData, drmInitData, language);
}
public static Format createAudioSampleFormat(String id, String sampleMimeType, int bitrate,
int maxInputSize, int channelCount, int sampleRate, int pcmEncoding, int encoderDelay,
int encoderPadding, List<byte[]> initializationData, DrmInitData drmInitData,
String language) {
return new Format(id, null, sampleMimeType, bitrate, maxInputSize, NO_VALUE, NO_VALUE, NO_VALUE, return new Format(id, null, sampleMimeType, bitrate, maxInputSize, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, channelCount, sampleRate, encoderDelay, encoderPadding, language, NO_VALUE, NO_VALUE, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, false); language, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, false);
} }
// Text. // Text.
@ -216,7 +231,7 @@ public final class Format {
public static Format createTextContainerFormat(String id, String containerMimeType, public static Format createTextContainerFormat(String id, String containerMimeType,
String sampleMimeType, int bitrate, String language) { String sampleMimeType, int bitrate, String language) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language,
OFFSET_SAMPLE_RELATIVE, null, null, false); OFFSET_SAMPLE_RELATIVE, null, null, false);
} }
@ -229,7 +244,7 @@ public final class Format {
public static Format createTextSampleFormat(String id, String sampleMimeType, int bitrate, public static Format createTextSampleFormat(String id, String sampleMimeType, int bitrate,
String language, DrmInitData drmInitData, long subsampleOffsetUs) { String language, DrmInitData drmInitData, long subsampleOffsetUs) {
return new Format(id, null, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, return new Format(id, null, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language,
subsampleOffsetUs, null, drmInitData, false); subsampleOffsetUs, null, drmInitData, false);
} }
@ -238,7 +253,7 @@ public final class Format {
public static Format createImageSampleFormat(String id, String sampleMimeType, int bitrate, public static Format createImageSampleFormat(String id, String sampleMimeType, int bitrate,
List<byte[]> initializationData, String language, DrmInitData drmInitData) { List<byte[]> initializationData, String language, DrmInitData drmInitData) {
return new Format(id, null, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, return new Format(id, null, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language,
OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, false); OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, false);
} }
@ -247,22 +262,22 @@ public final class Format {
public static Format createContainerFormat(String id, String containerMimeType, public static Format createContainerFormat(String id, String containerMimeType,
String sampleMimeType, int bitrate) { String sampleMimeType, int bitrate) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null,
OFFSET_SAMPLE_RELATIVE, null, null, false); OFFSET_SAMPLE_RELATIVE, null, null, false);
} }
public static Format createSampleFormat(String id, String sampleMimeType, int bitrate, public static Format createSampleFormat(String id, String sampleMimeType, int bitrate,
DrmInitData drmInitData) { DrmInitData drmInitData) {
return new Format(id, null, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, return new Format(id, null, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null,
null, drmInitData, false); OFFSET_SAMPLE_RELATIVE, null, drmInitData, false);
} }
/* package */ Format(String id, String containerMimeType, String sampleMimeType, /* package */ Format(String id, String containerMimeType, String sampleMimeType,
int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees, int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees,
float pixelWidthHeightRatio, int channelCount, int sampleRate, int encoderDelay, float pixelWidthHeightRatio, int channelCount, int sampleRate, int pcmEncoding,
int encoderPadding, String language, long subsampleOffsetUs, List<byte[]> initializationData, int encoderDelay, int encoderPadding, String language, long subsampleOffsetUs,
DrmInitData drmInitData, boolean requiresSecureDecryption) { List<byte[]> initializationData, DrmInitData drmInitData, boolean requiresSecureDecryption) {
this.id = id; this.id = id;
this.containerMimeType = containerMimeType; this.containerMimeType = containerMimeType;
this.sampleMimeType = sampleMimeType; this.sampleMimeType = sampleMimeType;
@ -275,6 +290,7 @@ public final class Format {
this.pixelWidthHeightRatio = pixelWidthHeightRatio; this.pixelWidthHeightRatio = pixelWidthHeightRatio;
this.channelCount = channelCount; this.channelCount = channelCount;
this.sampleRate = sampleRate; this.sampleRate = sampleRate;
this.pcmEncoding = pcmEncoding;
this.encoderDelay = encoderDelay; this.encoderDelay = encoderDelay;
this.encoderPadding = encoderPadding; this.encoderPadding = encoderPadding;
this.language = language; this.language = language;
@ -288,14 +304,14 @@ public final class Format {
public Format copyWithMaxInputSize(int maxInputSize) { public Format copyWithMaxInputSize(int maxInputSize) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width, return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData, pcmEncoding, encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
drmInitData, requiresSecureDecryption); drmInitData, requiresSecureDecryption);
} }
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) { public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width, return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData, pcmEncoding, encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
drmInitData, requiresSecureDecryption); drmInitData, requiresSecureDecryption);
} }
@ -303,21 +319,21 @@ public final class Format {
String language) { String language) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width, return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData, pcmEncoding, encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
drmInitData, requiresSecureDecryption); drmInitData, requiresSecureDecryption);
} }
public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) { public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width, return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData, pcmEncoding, encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
drmInitData, requiresSecureDecryption); drmInitData, requiresSecureDecryption);
} }
public Format copyWithDrmInitData(DrmInitData drmInitData) { public Format copyWithDrmInitData(DrmInitData drmInitData) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width, return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData, pcmEncoding, encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
drmInitData, requiresSecureDecryption); drmInitData, requiresSecureDecryption);
} }
@ -379,8 +395,6 @@ public final class Format {
result = 31 * result + height; result = 31 * result + height;
result = 31 * result + channelCount; result = 31 * result + channelCount;
result = 31 * result + sampleRate; result = 31 * result + sampleRate;
result = 31 * result + encoderDelay;
result = 31 * result + encoderPadding;
result = 31 * result + (language == null ? 0 : language.hashCode()); result = 31 * result + (language == null ? 0 : language.hashCode());
result = 31 * result + (drmInitData == null ? 0 : drmInitData.hashCode()); result = 31 * result + (drmInitData == null ? 0 : drmInitData.hashCode());
hashCode = result; hashCode = result;
@ -403,8 +417,8 @@ public final class Format {
|| rotationDegrees != other.rotationDegrees || rotationDegrees != other.rotationDegrees
|| pixelWidthHeightRatio != other.pixelWidthHeightRatio || pixelWidthHeightRatio != other.pixelWidthHeightRatio
|| channelCount != other.channelCount || sampleRate != other.sampleRate || channelCount != other.channelCount || sampleRate != other.sampleRate
|| encoderDelay != other.encoderDelay || encoderPadding != other.encoderPadding || pcmEncoding != other.pcmEncoding || encoderDelay != other.encoderDelay
|| subsampleOffsetUs != other.subsampleOffsetUs || encoderPadding != other.encoderPadding || subsampleOffsetUs != other.subsampleOffsetUs
|| !Util.areEqual(id, other.id) || !Util.areEqual(language, other.language) || !Util.areEqual(id, other.id) || !Util.areEqual(language, other.language)
|| !Util.areEqual(containerMimeType, other.containerMimeType) || !Util.areEqual(containerMimeType, other.containerMimeType)
|| !Util.areEqual(sampleMimeType, other.sampleMimeType) || !Util.areEqual(sampleMimeType, other.sampleMimeType)

View File

@ -333,10 +333,11 @@ public final class FrameworkSampleSource implements SampleSource {
initializationData.add(data); initializationData.add(data);
buffer.flip(); buffer.flip();
} }
int pcmEncoding = MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : Format.NO_VALUE;
Format format = new Format(Integer.toString(index), null, mimeType, Format.NO_VALUE, Format format = new Format(Integer.toString(index), null, mimeType, Format.NO_VALUE,
maxInputSize, width, height, frameRate, rotationDegrees, Format.NO_VALUE, channelCount, maxInputSize, width, height, frameRate, rotationDegrees, Format.NO_VALUE, channelCount,
sampleRate, encoderDelay, encoderPadding, language, Format.OFFSET_SAMPLE_RELATIVE, sampleRate, pcmEncoding, encoderDelay, encoderPadding, language,
initializationData, drmInitData, false); Format.OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, false);
format.setFrameworkMediaFormatV16(mediaFormat); format.setFrameworkMediaFormatV16(mediaFormat);
return format; return format;
} }

View File

@ -70,6 +70,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
private boolean passthroughEnabled; private boolean passthroughEnabled;
private android.media.MediaFormat passthroughMediaFormat; private android.media.MediaFormat passthroughMediaFormat;
private int pcmEncoding;
private int audioSessionId; private int audioSessionId;
private long currentPositionUs; private long currentPositionUs;
private boolean allowPositionDiscontinuity; private boolean allowPositionDiscontinuity;
@ -234,6 +235,15 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
return this; return this;
} }
@Override
protected void onInputFormatChanged(FormatHolder holder) throws ExoPlaybackException {
super.onInputFormatChanged(holder);
// If the input format is anything other than PCM then we assume that the audio decoder will
// output 16-bit PCM.
pcmEncoding = MimeTypes.AUDIO_RAW.equals(holder.format.sampleMimeType)
? holder.format.pcmEncoding : C.ENCODING_PCM_16BIT;
}
@Override @Override
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) { protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) {
boolean passthrough = passthroughMediaFormat != null; boolean passthrough = passthroughMediaFormat != null;
@ -243,7 +253,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
android.media.MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat; android.media.MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat;
int channelCount = format.getInteger(android.media.MediaFormat.KEY_CHANNEL_COUNT); int channelCount = format.getInteger(android.media.MediaFormat.KEY_CHANNEL_COUNT);
int sampleRate = format.getInteger(android.media.MediaFormat.KEY_SAMPLE_RATE); int sampleRate = format.getInteger(android.media.MediaFormat.KEY_SAMPLE_RATE);
audioTrack.configure(mimeType, channelCount, sampleRate); audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding);
} }
/** /**

View File

@ -199,7 +199,8 @@ public final class AudioTrack {
private android.media.AudioTrack audioTrack; private android.media.AudioTrack audioTrack;
private int sampleRate; private int sampleRate;
private int channelConfig; private int channelConfig;
private int encoding; private int sourceEncoding;
private int targetEncoding;
private boolean passthrough; private boolean passthrough;
private int pcmFrameSize; private int pcmFrameSize;
private int bufferSize; private int bufferSize;
@ -224,7 +225,10 @@ public final class AudioTrack {
private byte[] temporaryBuffer; private byte[] temporaryBuffer;
private int temporaryBufferOffset; private int temporaryBufferOffset;
private ByteBuffer currentBuffer; private ByteBuffer currentSourceBuffer;
private ByteBuffer resampledBuffer;
private boolean useResampledBuffer;
/** /**
* Creates an audio track with default audio capabilities (no encoded audio passthrough support). * Creates an audio track with default audio capabilities (no encoded audio passthrough support).
@ -336,9 +340,11 @@ public final class AudioTrack {
* @param mimeType The mime type. * @param mimeType The mime type.
* @param channelCount The number of channels. * @param channelCount The number of channels.
* @param sampleRate The sample rate in Hz. * @param sampleRate The sample rate in Hz.
* @param pcmEncoding For PCM formats, the encoding used. One of {@link C#ENCODING_PCM_16BIT},
* {@link C#ENCODING_PCM_16BIT} and {@link C#ENCODING_PCM_24BIT}.
*/ */
public void configure(String mimeType, int channelCount, int sampleRate) { public void configure(String mimeType, int channelCount, int sampleRate, int pcmEncoding) {
configure(mimeType, channelCount, sampleRate, 0); configure(mimeType, channelCount, sampleRate, pcmEncoding, 0);
} }
/** /**
@ -347,10 +353,12 @@ public final class AudioTrack {
* @param mimeType The mime type. * @param mimeType The mime type.
* @param channelCount The number of channels. * @param channelCount The number of channels.
* @param sampleRate The sample rate in Hz. * @param sampleRate The sample rate in Hz.
* @param pcmEncoding For PCM formats, the encoding used. One of {@link C#ENCODING_PCM_16BIT},
* {@link C#ENCODING_PCM_16BIT} and {@link C#ENCODING_PCM_24BIT}.
* @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a * @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a
* suitable buffer size automatically. * suitable buffer size automatically.
*/ */
public void configure(String mimeType, int channelCount, int sampleRate, public void configure(String mimeType, int channelCount, int sampleRate, int pcmEncoding,
int specifiedBufferSize) { int specifiedBufferSize) {
int channelConfig; int channelConfig;
switch (channelCount) { switch (channelCount) {
@ -381,37 +389,48 @@ public final class AudioTrack {
default: default:
throw new IllegalArgumentException("Unsupported channel count: " + channelCount); throw new IllegalArgumentException("Unsupported channel count: " + channelCount);
} }
boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType); boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType);
int encoding = passthrough ? getEncodingForMimeType(mimeType) : AudioFormat.ENCODING_PCM_16BIT; int sourceEncoding;
if (isInitialized() && this.sampleRate == sampleRate && this.channelConfig == channelConfig if (passthrough) {
&& this.encoding == encoding) { sourceEncoding = getEncodingForMimeType(mimeType);
// We already have an audio track with the correct sample rate, encoding and channel config. } else if (pcmEncoding == C.ENCODING_PCM_8BIT || pcmEncoding == C.ENCODING_PCM_16BIT
|| pcmEncoding == C.ENCODING_PCM_24BIT) {
sourceEncoding = pcmEncoding;
} else {
throw new IllegalArgumentException("Unsupported PCM encoding: " + pcmEncoding);
}
if (isInitialized() && this.sourceEncoding == sourceEncoding && this.sampleRate == sampleRate
&& this.channelConfig == channelConfig) {
// We already have an audio track with the correct sample rate, channel config and encoding.
return; return;
} }
reset(); reset();
this.encoding = encoding; this.sourceEncoding = sourceEncoding;
this.passthrough = passthrough; this.passthrough = passthrough;
this.sampleRate = sampleRate; this.sampleRate = sampleRate;
this.channelConfig = channelConfig; this.channelConfig = channelConfig;
pcmFrameSize = 2 * channelCount; // 2 bytes per 16 bit sample * number of channels. targetEncoding = passthrough ? sourceEncoding : C.ENCODING_PCM_16BIT;
pcmFrameSize = 2 * channelCount; // 2 bytes per 16-bit sample * number of channels.
if (specifiedBufferSize != 0) { if (specifiedBufferSize != 0) {
bufferSize = specifiedBufferSize; bufferSize = specifiedBufferSize;
} else if (passthrough) { } else if (passthrough) {
// TODO: Set the minimum buffer size using getMinBufferSize when it takes the encoding into // TODO: Set the minimum buffer size using getMinBufferSize when it takes the encoding into
// account. [Internal: b/25181305] // account. [Internal: b/25181305]
if (encoding == C.ENCODING_AC3 || encoding == C.ENCODING_E_AC3) { if (targetEncoding == C.ENCODING_AC3 || targetEncoding == C.ENCODING_E_AC3) {
// AC-3 allows bitrates up to 640 kbit/s. // AC-3 allows bitrates up to 640 kbit/s.
bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 80 * 1024 / C.MICROS_PER_SECOND); bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 80 * 1024 / C.MICROS_PER_SECOND);
} else { // encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD } else /* (targetEncoding == C.ENCODING_DTS || targetEncoding == C.ENCODING_DTS_HD */ {
// DTS allows an 'open' bitrate, but we assume the maximum listed value: 1536 kbit/s. // DTS allows an 'open' bitrate, but we assume the maximum listed value: 1536 kbit/s.
bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 192 * 1024 / C.MICROS_PER_SECOND); bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 192 * 1024 / C.MICROS_PER_SECOND);
} }
} else { } else {
int minBufferSize = int minBufferSize =
android.media.AudioTrack.getMinBufferSize(sampleRate, channelConfig, encoding); android.media.AudioTrack.getMinBufferSize(sampleRate, channelConfig, targetEncoding);
Assertions.checkState(minBufferSize != android.media.AudioTrack.ERROR_BAD_VALUE); Assertions.checkState(minBufferSize != android.media.AudioTrack.ERROR_BAD_VALUE);
int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR; int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR;
int minAppBufferSize = (int) durationUsToFrames(MIN_BUFFER_DURATION_US) * pcmFrameSize; int minAppBufferSize = (int) durationUsToFrames(MIN_BUFFER_DURATION_US) * pcmFrameSize;
@ -449,12 +468,12 @@ public final class AudioTrack {
releasingConditionVariable.block(); releasingConditionVariable.block();
if (sessionId == SESSION_ID_NOT_SET) { if (sessionId == SESSION_ID_NOT_SET) {
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, encoding, audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig,
bufferSize, android.media.AudioTrack.MODE_STREAM); targetEncoding, bufferSize, android.media.AudioTrack.MODE_STREAM);
} else { } else {
// Re-attach to the same audio session. // Re-attach to the same audio session.
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, encoding, audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig,
bufferSize, android.media.AudioTrack.MODE_STREAM, sessionId); targetEncoding, bufferSize, android.media.AudioTrack.MODE_STREAM, sessionId);
} }
checkAudioTrackInitialized(); checkAudioTrackInitialized();
@ -470,7 +489,7 @@ public final class AudioTrack {
if (keepSessionIdAudioTrack == null) { if (keepSessionIdAudioTrack == null) {
int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE. int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE.
int channelConfig = AudioFormat.CHANNEL_OUT_MONO; int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
int encoding = AudioFormat.ENCODING_PCM_16BIT; int encoding = C.ENCODING_PCM_16BIT;
int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback. int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback.
keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate, keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate,
channelConfig, encoding, bufferSize, android.media.AudioTrack.MODE_STATIC, sessionId); channelConfig, encoding, bufferSize, android.media.AudioTrack.MODE_STATIC, sessionId);
@ -551,15 +570,9 @@ public final class AudioTrack {
* @throws WriteException If an error occurs writing the audio data. * @throws WriteException If an error occurs writing the audio data.
*/ */
public int handleBuffer(ByteBuffer buffer, long presentationTimeUs) throws WriteException { public int handleBuffer(ByteBuffer buffer, long presentationTimeUs) throws WriteException {
boolean isNewBuffer = currentBuffer == null; boolean isNewSourceBuffer = currentSourceBuffer == null;
Assertions.checkState(isNewBuffer || currentBuffer == buffer); Assertions.checkState(isNewSourceBuffer || currentSourceBuffer == buffer);
currentBuffer = buffer; currentSourceBuffer = buffer;
int bytesRemaining = buffer.remaining();
if (bytesRemaining == 0) {
currentBuffer = null;
return RESULT_BUFFER_CONSUMED;
}
if (needsPassthroughWorkarounds()) { if (needsPassthroughWorkarounds()) {
// An AC-3 audio track continues to play data written while it is paused. Stop writing so its // An AC-3 audio track continues to play data written while it is paused. Stop writing so its
@ -578,11 +591,26 @@ public final class AudioTrack {
} }
int result = 0; int result = 0;
if (isNewBuffer) { if (isNewSourceBuffer) {
// We're seeing this buffer for the first time. // We're seeing this buffer for the first time.
if (!currentSourceBuffer.hasRemaining()) {
// The buffer is empty.
currentSourceBuffer = null;
return RESULT_BUFFER_CONSUMED;
}
useResampledBuffer = targetEncoding != sourceEncoding;
if (useResampledBuffer) {
Assertions.checkState(targetEncoding == C.ENCODING_PCM_16BIT);
// Resample the buffer to get the data in the target encoding.
resampledBuffer = resampleTo16BitPcm(currentSourceBuffer, sourceEncoding, resampledBuffer);
buffer = resampledBuffer;
}
if (passthrough && framesPerEncodedSample == 0) { if (passthrough && framesPerEncodedSample == 0) {
// If this is the first encoded sample, calculate the sample size in frames. // If this is the first encoded sample, calculate the sample size in frames.
framesPerEncodedSample = getFramesPerEncodedSample(encoding, buffer); framesPerEncodedSample = getFramesPerEncodedSample(targetEncoding, buffer);
} }
if (startMediaTimeState == START_NOT_SET) { if (startMediaTimeState == START_NOT_SET) {
startMediaTimeUs = Math.max(0, presentationTimeUs); startMediaTimeUs = Math.max(0, presentationTimeUs);
@ -607,6 +635,7 @@ public final class AudioTrack {
} }
if (Util.SDK_INT < 21) { if (Util.SDK_INT < 21) {
// Copy {@code buffer} into {@code temporaryBuffer}. // Copy {@code buffer} into {@code temporaryBuffer}.
int bytesRemaining = buffer.remaining();
if (temporaryBuffer == null || temporaryBuffer.length < bytesRemaining) { if (temporaryBuffer == null || temporaryBuffer.length < bytesRemaining) {
temporaryBuffer = new byte[bytesRemaining]; temporaryBuffer = new byte[bytesRemaining];
} }
@ -617,6 +646,8 @@ public final class AudioTrack {
} }
} }
buffer = useResampledBuffer ? resampledBuffer : buffer;
int bytesRemaining = buffer.remaining();
int bytesWritten = 0; int bytesWritten = 0;
if (Util.SDK_INT < 21) { // passthrough == false if (Util.SDK_INT < 21) { // passthrough == false
// Work out how many bytes we can write without the risk of blocking. // Work out how many bytes we can write without the risk of blocking.
@ -646,7 +677,7 @@ public final class AudioTrack {
if (passthrough) { if (passthrough) {
submittedEncodedFrames += framesPerEncodedSample; submittedEncodedFrames += framesPerEncodedSample;
} }
currentBuffer = null; currentSourceBuffer = null;
result |= RESULT_BUFFER_CONSUMED; result |= RESULT_BUFFER_CONSUMED;
} }
return result; return result;
@ -661,12 +692,6 @@ public final class AudioTrack {
} }
} }
@TargetApi(21)
private static int writeNonBlockingV21(
android.media.AudioTrack audioTrack, ByteBuffer buffer, int size) {
return audioTrack.write(buffer, size, android.media.AudioTrack.WRITE_NON_BLOCKING);
}
/** /**
* Returns whether the audio track has more data pending that will be played back. * Returns whether the audio track has more data pending that will be played back.
*/ */
@ -707,16 +732,6 @@ public final class AudioTrack {
} }
} }
@TargetApi(21)
private static void setAudioTrackVolumeV21(android.media.AudioTrack audioTrack, float volume) {
audioTrack.setVolume(volume);
}
@SuppressWarnings("deprecation")
private static void setAudioTrackVolumeV3(android.media.AudioTrack audioTrack, float volume) {
audioTrack.setStereoVolume(volume, volume);
}
/** /**
* Pauses playback. * Pauses playback.
*/ */
@ -737,7 +752,7 @@ public final class AudioTrack {
submittedPcmBytes = 0; submittedPcmBytes = 0;
submittedEncodedFrames = 0; submittedEncodedFrames = 0;
framesPerEncodedSample = 0; framesPerEncodedSample = 0;
currentBuffer = null; currentSourceBuffer = null;
startMediaTimeState = START_NOT_SET; startMediaTimeState = START_NOT_SET;
latencyUs = 0; latencyUs = 0;
resetSyncParams(); resetSyncParams();
@ -937,7 +952,8 @@ public final class AudioTrack {
* See [Internal: b/18899620, b/19187573, b/21145353]. * See [Internal: b/18899620, b/19187573, b/21145353].
*/ */
private boolean needsPassthroughWorkarounds() { private boolean needsPassthroughWorkarounds() {
return Util.SDK_INT < 23 && (encoding == C.ENCODING_AC3 || encoding == C.ENCODING_E_AC3); return Util.SDK_INT < 23
&& (targetEncoding == C.ENCODING_AC3 || targetEncoding == C.ENCODING_E_AC3);
} }
/** /**
@ -952,6 +968,66 @@ public final class AudioTrack {
&& audioTrack.getPlaybackHeadPosition() == 0; && audioTrack.getPlaybackHeadPosition() == 0;
} }
/**
* Converts the provided buffer into 16-bit PCM.
*
* @param buffer The buffer containing the data to convert.
* @param sourceEncoding The data encoding.
* @param out A buffer into which the output should be written, if its capacity is sufficient.
* @return The 16-bit PCM output. Different to the out parameter if null was passed, or if the
* capacity was insufficient for the output.
*/
private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, int sourceEncoding,
ByteBuffer out) {
int offset = buffer.position();
int limit = buffer.limit();
int size = limit - offset;
int resampledSize;
switch (sourceEncoding) {
case C.ENCODING_PCM_8BIT:
resampledSize = size * 2;
break;
case C.ENCODING_PCM_24BIT:
resampledSize = (size / 3) * 2;
break;
default:
// Never happens.
throw new IllegalStateException();
}
ByteBuffer resampledBuffer = out;
if (resampledBuffer == null || resampledBuffer.capacity() < resampledSize) {
resampledBuffer = ByteBuffer.allocateDirect(resampledSize);
}
resampledBuffer.position(0);
resampledBuffer.limit(resampledSize);
// Samples are little endian.
switch (sourceEncoding) {
case C.ENCODING_PCM_8BIT:
// 8->16 bit resampling. Shift each byte from [0, 256) to [-128, 128) and scale up.
for (int i = offset; i < limit; i++) {
resampledBuffer.put((byte) 0);
resampledBuffer.put((byte) ((buffer.get(i) & 0xFF) - 128));
}
break;
case C.ENCODING_PCM_24BIT:
// 24->16 bit resampling. Drop the least significant byte.
for (int i = offset; i < limit; i += 3) {
resampledBuffer.put(buffer.get(i + 1));
resampledBuffer.put(buffer.get(i + 2));
}
break;
default:
// Never happens.
throw new IllegalStateException();
}
resampledBuffer.position(0);
return resampledBuffer;
}
private static int getEncodingForMimeType(String mimeType) { private static int getEncodingForMimeType(String mimeType) {
switch (mimeType) { switch (mimeType) {
case MimeTypes.AUDIO_AC3: case MimeTypes.AUDIO_AC3:
@ -963,7 +1039,7 @@ public final class AudioTrack {
case MimeTypes.AUDIO_DTS_HD: case MimeTypes.AUDIO_DTS_HD:
return C.ENCODING_DTS_HD; return C.ENCODING_DTS_HD;
default: default:
return AudioFormat.ENCODING_INVALID; return C.ENCODING_INVALID;
} }
} }
@ -979,6 +1055,22 @@ public final class AudioTrack {
} }
} }
@TargetApi(21)
private static int writeNonBlockingV21(
android.media.AudioTrack audioTrack, ByteBuffer buffer, int size) {
return audioTrack.write(buffer, size, android.media.AudioTrack.WRITE_NON_BLOCKING);
}
@TargetApi(21)
private static void setAudioTrackVolumeV21(android.media.AudioTrack audioTrack, float volume) {
audioTrack.setVolume(volume);
}
@SuppressWarnings("deprecation")
private static void setAudioTrackVolumeV3(android.media.AudioTrack audioTrack, float volume) {
audioTrack.setStereoVolume(volume, volume);
}
/** /**
* Wraps an {@link android.media.AudioTrack} to expose useful utility methods. * Wraps an {@link android.media.AudioTrack} to expose useful utility methods.
*/ */

View File

@ -1207,6 +1207,7 @@ public final class MatroskaExtractor implements Extractor {
public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException { public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException {
String mimeType; String mimeType;
int maxInputSize = Format.NO_VALUE; int maxInputSize = Format.NO_VALUE;
int pcmEncoding = Format.NO_VALUE;
List<byte[]> initializationData = null; List<byte[]> initializationData = null;
switch (codecId) { switch (codecId) {
case CODEC_ID_VP8: case CODEC_ID_VP8:
@ -1291,13 +1292,15 @@ public final class MatroskaExtractor implements Extractor {
if (!parseMsAcmCodecPrivate(new ParsableByteArray(codecPrivate))) { if (!parseMsAcmCodecPrivate(new ParsableByteArray(codecPrivate))) {
throw new ParserException("Non-PCM MS/ACM is unsupported"); throw new ParserException("Non-PCM MS/ACM is unsupported");
} }
if (audioBitDepth != 16) { pcmEncoding = Util.getPcmEncoding(audioBitDepth);
if (pcmEncoding == C.ENCODING_INVALID) {
throw new ParserException("Unsupported PCM bit depth: " + audioBitDepth); throw new ParserException("Unsupported PCM bit depth: " + audioBitDepth);
} }
break; break;
case CODEC_ID_PCM_INT_LIT: case CODEC_ID_PCM_INT_LIT:
mimeType = MimeTypes.AUDIO_RAW; mimeType = MimeTypes.AUDIO_RAW;
if (audioBitDepth != 16) { pcmEncoding = Util.getPcmEncoding(audioBitDepth);
if (pcmEncoding == C.ENCODING_INVALID) {
throw new ParserException("Unsupported PCM bit depth: " + audioBitDepth); throw new ParserException("Unsupported PCM bit depth: " + audioBitDepth);
} }
break; break;
@ -1320,8 +1323,8 @@ public final class MatroskaExtractor implements Extractor {
// into the trackId passed when creating the formats. // into the trackId passed when creating the formats.
if (MimeTypes.isAudio(mimeType)) { if (MimeTypes.isAudio(mimeType)) {
format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType,
Format.NO_VALUE, maxInputSize, channelCount, sampleRate, initializationData, language, Format.NO_VALUE, maxInputSize, channelCount, sampleRate, pcmEncoding,
drmInitData); initializationData, drmInitData, language);
} else if (MimeTypes.isVideo(mimeType)) { } else if (MimeTypes.isVideo(mimeType)) {
if (displayUnit == Track.DISPLAY_UNIT_PIXELS) { if (displayUnit == Track.DISPLAY_UNIT_PIXELS) {
displayWidth = displayWidth == Format.NO_VALUE ? width : displayWidth; displayWidth = displayWidth == Format.NO_VALUE ? width : displayWidth;

View File

@ -127,7 +127,7 @@ public final class Mp3Extractor implements Extractor {
extractorOutput.seekMap(seeker); extractorOutput.seekMap(seeker);
trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType, trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType,
Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels, Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels,
synchronizedHeader.sampleRate, gaplessInfoHolder.encoderDelay, synchronizedHeader.sampleRate, Format.NO_VALUE, gaplessInfoHolder.encoderDelay,
gaplessInfoHolder.encoderPadding, null, null, null)); gaplessInfoHolder.encoderPadding, null, null, null));
} }
return readSample(input); return readSample(input);

View File

@ -939,8 +939,8 @@ import java.util.List;
|| atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl) || atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl)
&& childAtomType == Atom.TYPE_ddts) { && childAtomType == Atom.TYPE_ddts) {
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, language, Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, drmInitData,
drmInitData); language);
return; return;
} }
childAtomPosition += childAtomSize; childAtomPosition += childAtomSize;
@ -951,10 +951,13 @@ import java.util.List;
return; return;
} }
// TODO: Determine the correct PCM encoding.
int pcmEncoding = MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : Format.NO_VALUE;
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding,
initializationData == null ? null : Collections.singletonList(initializationData), initializationData == null ? null : Collections.singletonList(initializationData),
language, drmInitData); drmInitData, language);
} }
/** Returns the position of the esds box within a parent, or -1 if no esds box is found */ /** Returns the position of the esds box within a parent, or -1 if no esds box is found */

View File

@ -74,7 +74,7 @@ public final class WavExtractor implements Extractor, SeekMap {
} }
Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_RAW, Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_RAW,
wavHeader.getBitrate(), MAX_INPUT_SIZE, wavHeader.getNumChannels(), wavHeader.getBitrate(), MAX_INPUT_SIZE, wavHeader.getNumChannels(),
wavHeader.getSampleRateHz(), null, null, null); wavHeader.getSampleRateHz(), wavHeader.getEncoding(), null, null, null);
trackOutput.format(format); trackOutput.format(format);
bytesPerFrame = wavHeader.getBytesPerFrame(); bytesPerFrame = wavHeader.getBytesPerFrame();
} }

View File

@ -30,22 +30,22 @@ import com.google.android.exoplayer.C;
private final int blockAlignment; private final int blockAlignment;
/** Bits per sample for the audio data. */ /** Bits per sample for the audio data. */
private final int bitsPerSample; private final int bitsPerSample;
/** The pcm encoding */
private final int encoding;
/** Offset to the start of sample data. */ /** Offset to the start of sample data. */
private long dataStartPosition; private long dataStartPosition;
/** Total size in bytes of the sample data. */ /** Total size in bytes of the sample data. */
private long dataSize; private long dataSize;
public WavHeader( public WavHeader(int numChannels, int sampleRateHz, int averageBytesPerSecond, int blockAlignment,
int numChannels, int bitsPerSample, int encoding) {
int sampleRateHz,
int averageBytesPerSecond,
int blockAlignment,
int bitsPerSample) {
this.numChannels = numChannels; this.numChannels = numChannels;
this.sampleRateHz = sampleRateHz; this.sampleRateHz = sampleRateHz;
this.averageBytesPerSecond = averageBytesPerSecond; this.averageBytesPerSecond = averageBytesPerSecond;
this.blockAlignment = blockAlignment; this.blockAlignment = blockAlignment;
this.bitsPerSample = bitsPerSample; this.bitsPerSample = bitsPerSample;
this.encoding = encoding;
} }
/** Returns the duration in microseconds of this WAV. */ /** Returns the duration in microseconds of this WAV. */
@ -110,4 +110,10 @@ import com.google.android.exoplayer.C;
this.dataStartPosition = dataStartPosition; this.dataStartPosition = dataStartPosition;
this.dataSize = dataSize; this.dataSize = dataSize;
} }
/** Returns the PCM encoding. **/
public int getEncoding() {
return encoding;
}
} }

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer.extractor.wav; package com.google.android.exoplayer.extractor.wav;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
@ -87,8 +88,10 @@ import java.io.IOException;
throw new ParserException("Expected block alignment: " + expectedBlockAlignment + "; got: " throw new ParserException("Expected block alignment: " + expectedBlockAlignment + "; got: "
+ blockAlignment); + blockAlignment);
} }
if (bitsPerSample != 16) {
Log.e(TAG, "Only 16-bit WAVs are supported; got: " + bitsPerSample); int encoding = Util.getPcmEncoding(bitsPerSample);
if (encoding == C.ENCODING_INVALID) {
Log.e(TAG, "Unsupported WAV bit depth: " + bitsPerSample);
return null; return null;
} }
@ -100,8 +103,8 @@ import java.io.IOException;
// If present, skip extensionSize, validBitsPerSample, channelMask, subFormatGuid, ... // If present, skip extensionSize, validBitsPerSample, channelMask, subFormatGuid, ...
input.advancePeekPosition((int) chunkHeader.size - 16); input.advancePeekPosition((int) chunkHeader.size - 16);
return new WavHeader( return new WavHeader(numChannels, sampleRateHz, averageBytesPerSecond, blockAlignment,
numChannels, sampleRateHz, averageBytesPerSecond, blockAlignment, bitsPerSample); bitsPerSample, encoding);
} }
/** /**

View File

@ -80,7 +80,7 @@ public final class Ac3Util {
channelCount++; channelCount++;
} }
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, Format.NO_VALUE, return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, Format.NO_VALUE,
Format.NO_VALUE, channelCount, sampleRate, null, language, drmInitData); Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, language);
} }
/** /**
@ -107,7 +107,7 @@ public final class Ac3Util {
channelCount++; channelCount++;
} }
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, Format.NO_VALUE, return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, Format.NO_VALUE,
Format.NO_VALUE, channelCount, sampleRate, null, language, drmInitData); Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, language);
} }
/** /**
@ -138,7 +138,7 @@ public final class Ac3Util {
boolean lfeon = data.readBit(); boolean lfeon = data.readBit();
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, Format.NO_VALUE, return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, Format.NO_VALUE,
Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0),
SAMPLE_RATE_BY_FSCOD[fscod], null, language, drmInitData); SAMPLE_RATE_BY_FSCOD[fscod], null, drmInitData, language);
} }
/** /**
@ -166,7 +166,7 @@ public final class Ac3Util {
boolean lfeon = data.readBit(); boolean lfeon = data.readBit();
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, Format.NO_VALUE, return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, Format.NO_VALUE,
Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), sampleRate, null, Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), sampleRate, null,
language, drmInitData); drmInitData, language);
} }
/** /**

View File

@ -73,7 +73,7 @@ public final class DtsUtil {
frameBits.skipBits(10); // MIX, DYNF, TIMEF, AUXF, HDCD, EXT_AUDIO_ID, EXT_AUDIO, ASPF frameBits.skipBits(10); // MIX, DYNF, TIMEF, AUXF, HDCD, EXT_AUDIO_ID, EXT_AUDIO, ASPF
channelCount += frameBits.readBits(2) > 0 ? 1 : 0; // LFF channelCount += frameBits.readBits(2) > 0 ? 1 : 0; // LFF
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_DTS, bitrate, Format.NO_VALUE, return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_DTS, bitrate, Format.NO_VALUE,
channelCount, sampleRate, null, language, drmInitData); channelCount, sampleRate, null, drmInitData, language);
} }
/** /**

View File

@ -777,6 +777,27 @@ public final class Util {
} }
} }
/**
* Converts a sample bit depth to a corresponding PCM encoding constant.
*
* @param bitDepth The bit depth. Supported values are 8, 16 and 24.
* @return The corresponding encoding. One of {@link C#ENCODING_PCM_8BIT},
* {@link C#ENCODING_PCM_16BIT} and {@link C#ENCODING_PCM_24BIT}. If the bit depth is
* unsupported then {@link C#ENCODING_INVALID} is returned.
*/
public static int getPcmEncoding(int bitDepth) {
switch (bitDepth) {
case 8:
return C.ENCODING_PCM_8BIT;
case 16:
return C.ENCODING_PCM_16BIT;
case 24:
return C.ENCODING_PCM_24BIT;
default:
return C.ENCODING_INVALID;
}
}
/** /**
* Makes a best guess to infer the type from a file name. * Makes a best guess to infer the type from a file name.
* *

View File

@ -292,7 +292,8 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
int result = readSource(formatHolder, null); int result = readSource(formatHolder, null);
if (result == TrackStream.FORMAT_READ) { if (result == TrackStream.FORMAT_READ) {
format = formatHolder.format; format = formatHolder.format;
audioTrack.configure(MimeTypes.AUDIO_RAW, format.channelCount, format.sampleRate); audioTrack.configure(MimeTypes.AUDIO_RAW, format.channelCount, format.sampleRate,
C.ENCODING_PCM_16BIT);
return true; return true;
} }
return false; return false;