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,
streamInfo.bitRate(), Format.NO_VALUE, streamInfo.channels, streamInfo.sampleRate, null,
null, null);
streamInfo.bitRate(), Format.NO_VALUE, streamInfo.channels, streamInfo.sampleRate,
C.ENCODING_PCM_16BIT, null, null, null);
trackOutput.format(mediaFormat);
outputBuffer = new ParsableByteArray(streamInfo.maxDecodedFrameSize());

View File

@ -59,6 +59,26 @@ public final class C {
@SuppressWarnings("InlinedApi")
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
*/

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util;
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.
*/
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.
*/
@ -167,7 +174,7 @@ public final class Format {
String sampleMimeType, int bitrate, int width, int height, float frameRate,
List<byte[]> initializationData) {
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);
}
@ -182,8 +189,8 @@ public final class Format {
int maxInputSize, int width, int height, float frameRate, List<byte[]> initializationData,
int rotationDegrees, float pixelWidthHeightRatio, DrmInitData drmInitData) {
return new Format(id, null, sampleMimeType, bitrate, maxInputSize, width, height, frameRate,
rotationDegrees, pixelWidthHeightRatio, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null,
OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, false);
rotationDegrees, pixelWidthHeightRatio, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
null, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, false);
}
// Audio.
@ -192,23 +199,31 @@ public final class Format {
String sampleMimeType, int bitrate, int channelCount, int sampleRate,
List<byte[]> initializationData, String language) {
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,
OFFSET_SAMPLE_RELATIVE, initializationData, null, false);
NO_VALUE, NO_VALUE, NO_VALUE, channelCount, sampleRate, NO_VALUE, NO_VALUE, NO_VALUE,
language, OFFSET_SAMPLE_RELATIVE, initializationData, null, false);
}
public static Format createAudioSampleFormat(String id, String sampleMimeType, int bitrate,
int maxInputSize, int channelCount, int sampleRate, List<byte[]> initializationData,
String language, DrmInitData drmInitData) {
DrmInitData drmInitData, String language) {
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,
int maxInputSize, int channelCount, int sampleRate, int encoderDelay, int encoderPadding,
List<byte[]> initializationData, String language, DrmInitData drmInitData) {
int maxInputSize, int channelCount, int sampleRate, int pcmEncoding,
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,
NO_VALUE, NO_VALUE, channelCount, sampleRate, encoderDelay, encoderPadding, language,
OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, false);
NO_VALUE, NO_VALUE, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
language, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, false);
}
// Text.
@ -216,7 +231,7 @@ public final class Format {
public static Format createTextContainerFormat(String id, String containerMimeType,
String sampleMimeType, int bitrate, String language) {
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);
}
@ -229,7 +244,7 @@ public final class Format {
public static Format createTextSampleFormat(String id, String sampleMimeType, int bitrate,
String language, DrmInitData drmInitData, long subsampleOffsetUs) {
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);
}
@ -238,7 +253,7 @@ public final class Format {
public static Format createImageSampleFormat(String id, String sampleMimeType, int bitrate,
List<byte[]> initializationData, String language, DrmInitData drmInitData) {
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);
}
@ -247,22 +262,22 @@ public final class Format {
public static Format createContainerFormat(String id, String containerMimeType,
String sampleMimeType, int bitrate) {
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);
}
public static Format createSampleFormat(String id, String sampleMimeType, int bitrate,
DrmInitData drmInitData) {
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,
null, drmInitData, false);
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null,
OFFSET_SAMPLE_RELATIVE, null, drmInitData, false);
}
/* package */ Format(String id, String containerMimeType, String sampleMimeType,
int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees,
float pixelWidthHeightRatio, int channelCount, int sampleRate, int encoderDelay,
int encoderPadding, String language, long subsampleOffsetUs, List<byte[]> initializationData,
DrmInitData drmInitData, boolean requiresSecureDecryption) {
float pixelWidthHeightRatio, int channelCount, int sampleRate, int pcmEncoding,
int encoderDelay, int encoderPadding, String language, long subsampleOffsetUs,
List<byte[]> initializationData, DrmInitData drmInitData, boolean requiresSecureDecryption) {
this.id = id;
this.containerMimeType = containerMimeType;
this.sampleMimeType = sampleMimeType;
@ -275,6 +290,7 @@ public final class Format {
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
this.channelCount = channelCount;
this.sampleRate = sampleRate;
this.pcmEncoding = pcmEncoding;
this.encoderDelay = encoderDelay;
this.encoderPadding = encoderPadding;
this.language = language;
@ -288,14 +304,14 @@ public final class Format {
public Format copyWithMaxInputSize(int maxInputSize) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
pcmEncoding, encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
drmInitData, requiresSecureDecryption);
}
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
pcmEncoding, encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
drmInitData, requiresSecureDecryption);
}
@ -303,21 +319,21 @@ public final class Format {
String language) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
pcmEncoding, encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
drmInitData, requiresSecureDecryption);
}
public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
pcmEncoding, encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
drmInitData, requiresSecureDecryption);
}
public Format copyWithDrmInitData(DrmInitData drmInitData) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
pcmEncoding, encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
drmInitData, requiresSecureDecryption);
}
@ -379,8 +395,6 @@ public final class Format {
result = 31 * result + height;
result = 31 * result + channelCount;
result = 31 * result + sampleRate;
result = 31 * result + encoderDelay;
result = 31 * result + encoderPadding;
result = 31 * result + (language == null ? 0 : language.hashCode());
result = 31 * result + (drmInitData == null ? 0 : drmInitData.hashCode());
hashCode = result;
@ -403,8 +417,8 @@ public final class Format {
|| rotationDegrees != other.rotationDegrees
|| pixelWidthHeightRatio != other.pixelWidthHeightRatio
|| channelCount != other.channelCount || sampleRate != other.sampleRate
|| encoderDelay != other.encoderDelay || encoderPadding != other.encoderPadding
|| subsampleOffsetUs != other.subsampleOffsetUs
|| pcmEncoding != other.pcmEncoding || encoderDelay != other.encoderDelay
|| encoderPadding != other.encoderPadding || subsampleOffsetUs != other.subsampleOffsetUs
|| !Util.areEqual(id, other.id) || !Util.areEqual(language, other.language)
|| !Util.areEqual(containerMimeType, other.containerMimeType)
|| !Util.areEqual(sampleMimeType, other.sampleMimeType)

View File

@ -333,10 +333,11 @@ public final class FrameworkSampleSource implements SampleSource {
initializationData.add(data);
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,
maxInputSize, width, height, frameRate, rotationDegrees, Format.NO_VALUE, channelCount,
sampleRate, encoderDelay, encoderPadding, language, Format.OFFSET_SAMPLE_RELATIVE,
initializationData, drmInitData, false);
sampleRate, pcmEncoding, encoderDelay, encoderPadding, language,
Format.OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, false);
format.setFrameworkMediaFormatV16(mediaFormat);
return format;
}

View File

@ -70,6 +70,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
private boolean passthroughEnabled;
private android.media.MediaFormat passthroughMediaFormat;
private int pcmEncoding;
private int audioSessionId;
private long currentPositionUs;
private boolean allowPositionDiscontinuity;
@ -234,6 +235,15 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
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
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) {
boolean passthrough = passthroughMediaFormat != null;
@ -243,7 +253,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
android.media.MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat;
int channelCount = format.getInteger(android.media.MediaFormat.KEY_CHANNEL_COUNT);
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 int sampleRate;
private int channelConfig;
private int encoding;
private int sourceEncoding;
private int targetEncoding;
private boolean passthrough;
private int pcmFrameSize;
private int bufferSize;
@ -224,7 +225,10 @@ public final class AudioTrack {
private byte[] temporaryBuffer;
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).
@ -336,9 +340,11 @@ public final class AudioTrack {
* @param mimeType The mime type.
* @param channelCount The number of channels.
* @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) {
configure(mimeType, channelCount, sampleRate, 0);
public void configure(String mimeType, int channelCount, int sampleRate, int pcmEncoding) {
configure(mimeType, channelCount, sampleRate, pcmEncoding, 0);
}
/**
@ -347,10 +353,12 @@ public final class AudioTrack {
* @param mimeType The mime type.
* @param channelCount The number of channels.
* @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
* 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 channelConfig;
switch (channelCount) {
@ -381,37 +389,48 @@ public final class AudioTrack {
default:
throw new IllegalArgumentException("Unsupported channel count: " + channelCount);
}
boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType);
int encoding = passthrough ? getEncodingForMimeType(mimeType) : AudioFormat.ENCODING_PCM_16BIT;
if (isInitialized() && this.sampleRate == sampleRate && this.channelConfig == channelConfig
&& this.encoding == encoding) {
// We already have an audio track with the correct sample rate, encoding and channel config.
int sourceEncoding;
if (passthrough) {
sourceEncoding = getEncodingForMimeType(mimeType);
} 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;
}
reset();
this.encoding = encoding;
this.sourceEncoding = sourceEncoding;
this.passthrough = passthrough;
this.sampleRate = sampleRate;
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) {
bufferSize = specifiedBufferSize;
} else if (passthrough) {
// TODO: Set the minimum buffer size using getMinBufferSize when it takes the encoding into
// 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.
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.
bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 192 * 1024 / C.MICROS_PER_SECOND);
}
} else {
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);
int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR;
int minAppBufferSize = (int) durationUsToFrames(MIN_BUFFER_DURATION_US) * pcmFrameSize;
@ -449,12 +468,12 @@ public final class AudioTrack {
releasingConditionVariable.block();
if (sessionId == SESSION_ID_NOT_SET) {
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, encoding,
bufferSize, android.media.AudioTrack.MODE_STREAM);
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig,
targetEncoding, bufferSize, android.media.AudioTrack.MODE_STREAM);
} else {
// Re-attach to the same audio session.
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, encoding,
bufferSize, android.media.AudioTrack.MODE_STREAM, sessionId);
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig,
targetEncoding, bufferSize, android.media.AudioTrack.MODE_STREAM, sessionId);
}
checkAudioTrackInitialized();
@ -470,7 +489,7 @@ public final class AudioTrack {
if (keepSessionIdAudioTrack == null) {
int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE.
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.
keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate,
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.
*/
public int handleBuffer(ByteBuffer buffer, long presentationTimeUs) throws WriteException {
boolean isNewBuffer = currentBuffer == null;
Assertions.checkState(isNewBuffer || currentBuffer == buffer);
currentBuffer = buffer;
int bytesRemaining = buffer.remaining();
if (bytesRemaining == 0) {
currentBuffer = null;
return RESULT_BUFFER_CONSUMED;
}
boolean isNewSourceBuffer = currentSourceBuffer == null;
Assertions.checkState(isNewSourceBuffer || currentSourceBuffer == buffer);
currentSourceBuffer = buffer;
if (needsPassthroughWorkarounds()) {
// 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;
if (isNewBuffer) {
if (isNewSourceBuffer) {
// 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 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) {
startMediaTimeUs = Math.max(0, presentationTimeUs);
@ -607,6 +635,7 @@ public final class AudioTrack {
}
if (Util.SDK_INT < 21) {
// Copy {@code buffer} into {@code temporaryBuffer}.
int bytesRemaining = buffer.remaining();
if (temporaryBuffer == null || temporaryBuffer.length < bytesRemaining) {
temporaryBuffer = new byte[bytesRemaining];
}
@ -617,6 +646,8 @@ public final class AudioTrack {
}
}
buffer = useResampledBuffer ? resampledBuffer : buffer;
int bytesRemaining = buffer.remaining();
int bytesWritten = 0;
if (Util.SDK_INT < 21) { // passthrough == false
// Work out how many bytes we can write without the risk of blocking.
@ -646,7 +677,7 @@ public final class AudioTrack {
if (passthrough) {
submittedEncodedFrames += framesPerEncodedSample;
}
currentBuffer = null;
currentSourceBuffer = null;
result |= RESULT_BUFFER_CONSUMED;
}
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.
*/
@ -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.
*/
@ -737,7 +752,7 @@ public final class AudioTrack {
submittedPcmBytes = 0;
submittedEncodedFrames = 0;
framesPerEncodedSample = 0;
currentBuffer = null;
currentSourceBuffer = null;
startMediaTimeState = START_NOT_SET;
latencyUs = 0;
resetSyncParams();
@ -937,7 +952,8 @@ public final class AudioTrack {
* See [Internal: b/18899620, b/19187573, b/21145353].
*/
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;
}
/**
* 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) {
switch (mimeType) {
case MimeTypes.AUDIO_AC3:
@ -963,7 +1039,7 @@ public final class AudioTrack {
case MimeTypes.AUDIO_DTS_HD:
return C.ENCODING_DTS_HD;
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.
*/

View File

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

View File

@ -127,7 +127,7 @@ public final class Mp3Extractor implements Extractor {
extractorOutput.seekMap(seeker);
trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType,
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));
}
return readSample(input);

View File

@ -939,8 +939,8 @@ import java.util.List;
|| atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl)
&& childAtomType == Atom.TYPE_ddts) {
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, language,
drmInitData);
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, drmInitData,
language);
return;
}
childAtomPosition += childAtomSize;
@ -951,10 +951,13 @@ import java.util.List;
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,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding,
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 */

View File

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

View File

@ -30,22 +30,22 @@ import com.google.android.exoplayer.C;
private final int blockAlignment;
/** Bits per sample for the audio data. */
private final int bitsPerSample;
/** The pcm encoding */
private final int encoding;
/** Offset to the start of sample data. */
private long dataStartPosition;
/** Total size in bytes of the sample data. */
private long dataSize;
public WavHeader(
int numChannels,
int sampleRateHz,
int averageBytesPerSecond,
int blockAlignment,
int bitsPerSample) {
public WavHeader(int numChannels, int sampleRateHz, int averageBytesPerSecond, int blockAlignment,
int bitsPerSample, int encoding) {
this.numChannels = numChannels;
this.sampleRateHz = sampleRateHz;
this.averageBytesPerSecond = averageBytesPerSecond;
this.blockAlignment = blockAlignment;
this.bitsPerSample = bitsPerSample;
this.encoding = encoding;
}
/** Returns the duration in microseconds of this WAV. */
@ -110,4 +110,10 @@ import com.google.android.exoplayer.C;
this.dataStartPosition = dataStartPosition;
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;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.util.Assertions;
@ -87,8 +88,10 @@ import java.io.IOException;
throw new ParserException("Expected block alignment: " + expectedBlockAlignment + "; got: "
+ 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;
}
@ -100,8 +103,8 @@ import java.io.IOException;
// If present, skip extensionSize, validBitsPerSample, channelMask, subFormatGuid, ...
input.advancePeekPosition((int) chunkHeader.size - 16);
return new WavHeader(
numChannels, sampleRateHz, averageBytesPerSecond, blockAlignment, bitsPerSample);
return new WavHeader(numChannels, sampleRateHz, averageBytesPerSecond, blockAlignment,
bitsPerSample, encoding);
}
/**

View File

@ -80,7 +80,7 @@ public final class Ac3Util {
channelCount++;
}
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++;
}
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();
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, Format.NO_VALUE,
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();
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, Format.NO_VALUE,
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
channelCount += frameBits.readBits(2) > 0 ? 1 : 0; // LFF
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.
*

View File

@ -292,7 +292,8 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
int result = readSource(formatHolder, null);
if (result == TrackStream.FORMAT_READ) {
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 false;