Pass an array of BufferProcessors to the AudioTrack.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=146215966
This commit is contained in:
andrewlewis 2017-02-01 01:25:34 -08:00 committed by Oliver Woodman
parent feeec77407
commit 74acbe04e3
10 changed files with 238 additions and 147 deletions

View file

@ -19,8 +19,8 @@ import android.os.Handler;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes;
@ -43,21 +43,12 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
* @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.
*/
public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener) {
super(eventHandler, eventListener);
}
/**
* @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 audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio buffers
* before they are output.
*/
public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities) {
super(eventHandler, eventListener, audioCapabilities);
BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, bufferProcessors);
}
@Override

View file

@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.flac;
import android.os.Handler;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes;
@ -38,21 +38,12 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
* @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.
*/
public LibflacAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener) {
super(eventHandler, eventListener);
}
/**
* @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 audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio buffers
* before they are output.
*/
public LibflacAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities) {
super(eventHandler, eventListener, audioCapabilities);
BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, bufferProcessors);
}
@Override

View file

@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.opus;
import android.os.Handler;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
@ -40,35 +40,26 @@ public final class LibopusAudioRenderer extends SimpleDecoderAudioRenderer {
* @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 bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio buffers
* before they are output.
*/
public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener) {
super(eventHandler, eventListener);
public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, bufferProcessors);
}
/**
* @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 audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio
* buffers before they are output.
*/
public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities) {
super(eventHandler, eventListener, audioCapabilities);
}
/**
* @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 audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
*/
public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities, DrmSessionManager<ExoMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys) {
super(eventHandler, eventListener, audioCapabilities, drmSessionManager,
playClearSamplesWithoutKeys);
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys,
BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, null, drmSessionManager, playClearSamplesWithoutKeys,
bufferProcessors);
}
@Override

View file

@ -29,6 +29,7 @@ import android.view.SurfaceView;
import android.view.TextureView;
import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.drm.DrmSessionManager;
@ -624,7 +625,7 @@ public class SimpleExoPlayer implements ExoPlayer {
buildVideoRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
componentListener, allowedVideoJoiningTimeMs, out);
buildAudioRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
componentListener, out);
componentListener, buildBufferProcessors(), out);
buildTextRenderers(context, mainHandler, extensionRendererMode, componentListener, out);
buildMetadataRenderers(context, mainHandler, extensionRendererMode, componentListener, out);
buildMiscellaneousRenderers(context, mainHandler, extensionRendererMode, out);
@ -636,7 +637,7 @@ public class SimpleExoPlayer implements ExoPlayer {
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
* not be used for DRM protected playbacks.
* not be used for DRM protected playbacks.
* @param extensionRendererMode The extension renderer mode.
* @param eventListener An event listener.
* @param allowedVideoJoiningTimeMs The maximum duration in milliseconds for which video renderers
@ -681,17 +682,19 @@ public class SimpleExoPlayer implements ExoPlayer {
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
* not be used for DRM protected playbacks.
* not be used for DRM protected playbacks.
* @param extensionRendererMode The extension renderer mode.
* @param eventListener An event listener.
* @param bufferProcessors An array of {@link BufferProcessor}s which will process PCM audio
* buffers before they are output. May be empty.
* @param out An array to which the built renderers should be appended.
*/
protected void buildAudioRenderers(Context context, Handler mainHandler,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, AudioRendererEventListener eventListener,
ArrayList<Renderer> out) {
BufferProcessor[] bufferProcessors, ArrayList<Renderer> out) {
out.add(new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, drmSessionManager, true,
mainHandler, eventListener, AudioCapabilities.getCapabilities(context)));
mainHandler, eventListener, AudioCapabilities.getCapabilities(context), bufferProcessors));
if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
return;
@ -705,8 +708,9 @@ public class SimpleExoPlayer implements ExoPlayer {
Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener);
AudioRendererEventListener.class, BufferProcessor[].class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener,
bufferProcessors);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibopusAudioRenderer.");
} catch (ClassNotFoundException e) {
@ -719,8 +723,9 @@ public class SimpleExoPlayer implements ExoPlayer {
Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener);
AudioRendererEventListener.class, BufferProcessor[].class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener,
bufferProcessors);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibflacAudioRenderer.");
} catch (ClassNotFoundException e) {
@ -733,8 +738,9 @@ public class SimpleExoPlayer implements ExoPlayer {
Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener);
AudioRendererEventListener.class, BufferProcessor[].class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener,
bufferProcessors);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded FfmpegAudioRenderer.");
} catch (ClassNotFoundException e) {
@ -787,6 +793,14 @@ public class SimpleExoPlayer implements ExoPlayer {
// Do nothing.
}
/**
* Builds an array of {@link BufferProcessor}s which will process PCM audio buffers before they
* are output.
*/
protected BufferProcessor[] buildBufferProcessors() {
return new BufferProcessor[0];
}
// Internal methods.
private void removeSurfaceCallbacks() {

View file

@ -90,6 +90,21 @@ public final class AudioTrack {
}
/**
* Thrown when a failure occurs configuring the track.
*/
public static final class ConfigurationException extends Exception {
public ConfigurationException(Throwable cause) {
super(cause);
}
public ConfigurationException(String message) {
super(message);
}
}
/**
* Thrown when a failure occurs initializing an {@link android.media.AudioTrack}.
*/
@ -254,6 +269,7 @@ public final class AudioTrack {
public static boolean failOnSpuriousAudioTimestamp = false;
private final AudioCapabilities audioCapabilities;
private final BufferProcessor[] bufferProcessors;
private final Listener listener;
private final ConditionVariable releasingConditionVariable;
private final long[] playheadOffsets;
@ -267,12 +283,12 @@ public final class AudioTrack {
private android.media.AudioTrack audioTrack;
private int sampleRate;
private int channelConfig;
@C.StreamType
private int streamType;
@C.Encoding
private int inputEncoding;
private int encoding;
@C.Encoding
private int outputEncoding;
@C.StreamType
private int streamType;
private boolean passthrough;
private int pcmFrameSize;
private int bufferSize;
@ -303,8 +319,6 @@ public final class AudioTrack {
private byte[] preV21OutputBuffer;
private int preV21OutputBufferOffset;
private BufferProcessor resampler;
private boolean playing;
private int audioSessionId;
private boolean tunneling;
@ -312,11 +326,18 @@ public final class AudioTrack {
private long lastFeedElapsedRealtimeMs;
/**
* @param audioCapabilities The current audio capabilities.
* @param audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param bufferProcessors An array of {@link BufferProcessor}s which will process PCM audio
* buffers before they are output. May be empty.
* @param listener Listener for audio track events.
*/
public AudioTrack(AudioCapabilities audioCapabilities, Listener listener) {
public AudioTrack(AudioCapabilities audioCapabilities, BufferProcessor[] bufferProcessors,
Listener listener) {
this.audioCapabilities = audioCapabilities;
this.bufferProcessors = new BufferProcessor[bufferProcessors.length + 1];
this.bufferProcessors[0] = new ResamplingBufferProcessor();
System.arraycopy(bufferProcessors, 0, this.bufferProcessors, 1, bufferProcessors.length);
this.listener = listener;
releasingConditionVariable = new ConditionVariable(true);
if (Util.SDK_INT >= 18) {
@ -413,9 +434,23 @@ public final class AudioTrack {
* {@link C#ENCODING_PCM_32BIT}.
* @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a
* suitable buffer size automatically.
* @throws ConfigurationException If an error occurs configuring the track.
*/
public void configure(String mimeType, int channelCount, int sampleRate,
@C.PcmEncoding int pcmEncoding, int specifiedBufferSize) {
@C.PcmEncoding int pcmEncoding, int specifiedBufferSize) throws ConfigurationException {
boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType);
@C.Encoding int encoding = passthrough ? getEncodingForMimeType(mimeType) : pcmEncoding;
if (!passthrough) {
for (BufferProcessor bufferProcessor : bufferProcessors) {
try {
bufferProcessor.configure(sampleRate, channelCount, encoding);
} catch (BufferProcessor.UnhandledFormatException e) {
throw new ConfigurationException(e);
}
encoding = bufferProcessor.getOutputEncoding();
}
}
int channelConfig;
switch (channelCount) {
case 1:
@ -443,7 +478,7 @@ public final class AudioTrack {
channelConfig = C.CHANNEL_OUT_7POINT1_SURROUND;
break;
default:
throw new IllegalArgumentException("Unsupported channel count: " + channelCount);
throw new ConfigurationException("Unsupported channel count: " + channelCount);
}
// Workaround for overly strict channel configuration checks on nVidia Shield.
@ -461,25 +496,13 @@ public final class AudioTrack {
}
}
boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType);
// Workaround for Nexus Player not reporting support for mono passthrough.
// (See [Internal: b/34268671].)
if (Util.SDK_INT <= 25 && "fugu".equals(Util.DEVICE) && passthrough && channelCount == 1) {
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
}
@C.Encoding int inputEncoding;
if (passthrough) {
inputEncoding = getEncodingForMimeType(mimeType);
} else if (pcmEncoding == C.ENCODING_PCM_8BIT || pcmEncoding == C.ENCODING_PCM_16BIT
|| pcmEncoding == C.ENCODING_PCM_24BIT || pcmEncoding == C.ENCODING_PCM_32BIT) {
inputEncoding = pcmEncoding;
} else {
throw new IllegalArgumentException("Unsupported PCM encoding: " + pcmEncoding);
}
if (isInitialized() && this.inputEncoding == inputEncoding && this.sampleRate == sampleRate
if (isInitialized() && this.encoding == encoding && this.sampleRate == sampleRate
&& this.channelConfig == channelConfig) {
// We already have an audio track with the correct sample rate, channel config and encoding.
return;
@ -487,15 +510,12 @@ public final class AudioTrack {
reset();
this.inputEncoding = inputEncoding;
this.encoding = encoding;
this.passthrough = passthrough;
this.sampleRate = sampleRate;
this.channelConfig = channelConfig;
pcmFrameSize = 2 * channelCount; // 2 bytes per 16-bit sample * number of channels.
outputEncoding = passthrough ? inputEncoding : C.ENCODING_PCM_16BIT;
resampler = outputEncoding != inputEncoding ? new ResamplingBufferProcessor(inputEncoding)
: null;
outputEncoding = passthrough ? encoding : C.ENCODING_PCM_16BIT;
if (specifiedBufferSize != 0) {
bufferSize = specifiedBufferSize;
@ -684,8 +704,12 @@ public final class AudioTrack {
}
inputBuffer = buffer;
outputBuffer = resampler != null ? resampler.handleBuffer(inputBuffer, outputBuffer)
: inputBuffer;
if (!passthrough) {
for (BufferProcessor bufferProcessor : bufferProcessors) {
buffer = bufferProcessor.handleBuffer(buffer);
}
}
outputBuffer = buffer;
if (Util.SDK_INT < 21) {
int bytesRemaining = outputBuffer.remaining();
if (preV21OutputBuffer == null || preV21OutputBuffer.length < bytesRemaining) {
@ -886,6 +910,9 @@ public final class AudioTrack {
framesPerEncodedSample = 0;
inputBuffer = null;
avSyncHeader = null;
for (BufferProcessor bufferProcessor : bufferProcessors) {
bufferProcessor.flush();
}
bytesUntilNextAvSync = 0;
startMediaTimeState = START_NOT_SET;
latencyUs = 0;
@ -919,6 +946,9 @@ public final class AudioTrack {
public void release() {
reset();
releaseKeepSessionIdAudioTrack();
for (BufferProcessor bufferProcessor : bufferProcessors) {
bufferProcessor.release();
}
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
playing = false;
}

View file

@ -15,23 +15,60 @@
*/
package com.google.android.exoplayer2.audio;
import com.google.android.exoplayer2.C;
import java.nio.ByteBuffer;
/**
* Interface for processors of buffers, for use with {@link AudioTrack}.
* Interface for processors of audio buffers.
*/
public interface BufferProcessor {
/**
* Processes the data in the specified input buffer in its entirety. Populates {@code output} with
* processed data if is not {@code null} and has sufficient capacity. Otherwise a different buffer
* will be populated and returned.
* Exception thrown when a processor can't be configured for a given input format.
*/
final class UnhandledFormatException extends Exception {
public UnhandledFormatException(int sampleRateHz, int channelCount, @C.Encoding int encoding) {
super("Unhandled format: " + sampleRateHz + " Hz, " + channelCount + " channels in encoding "
+ encoding);
}
}
/**
* Configures this processor to take input buffers with the specified format.
*
* @param sampleRateHz The sample rate of input audio in Hz.
* @param channelCount The number of interleaved channels in input audio.
* @param encoding The encoding of input audio.
* @throws UnhandledFormatException Thrown if the specified format can't be handled as input.
*/
void configure(int sampleRateHz, int channelCount, @C.Encoding int encoding)
throws UnhandledFormatException;
/**
* Returns the encoding used in buffers output by this processor.
*/
@C.Encoding
int getOutputEncoding();
/**
* Processes the data in the specified input buffer in its entirety.
*
* @param input A buffer containing the input data to process.
* @param output A buffer into which the output should be written, if its capacity is sufficient.
* @return The processed output. Different to {@code output} if null was passed, or if its
* capacity was insufficient.
* @return A buffer containing the processed output. This may be the same as the input buffer if
* no processing was required.
*/
ByteBuffer handleBuffer(ByteBuffer input, ByteBuffer output);
ByteBuffer handleBuffer(ByteBuffer input);
/**
* Clears any state in preparation for receiving a new stream of buffers.
*/
void flush();
/**
* Releases any resources associated with this instance.
*/
void release();
}

View file

@ -121,13 +121,16 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio buffers
* before they are output.
*/
public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys, Handler eventHandler,
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities) {
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
BufferProcessor... bufferProcessors) {
super(C.TRACK_TYPE_AUDIO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys);
audioTrack = new AudioTrack(audioCapabilities, new AudioTrackListener());
audioTrack = new AudioTrack(audioCapabilities, bufferProcessors, new AudioTrackListener());
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
}
@ -219,14 +222,19 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
@Override
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) {
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat)
throws ExoPlaybackException {
boolean passthrough = passthroughMediaFormat != null;
String mimeType = passthrough ? passthroughMediaFormat.getString(MediaFormat.KEY_MIME)
: MimeTypes.AUDIO_RAW;
MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat;
int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0);
try {
audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0);
} catch (AudioTrack.ConfigurationException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
}
/**

View file

@ -21,35 +21,47 @@ import com.google.android.exoplayer2.util.Assertions;
import java.nio.ByteBuffer;
/**
* A {@link BufferProcessor} that converts PCM input buffers from a specified input bit depth to
* {@link C#ENCODING_PCM_16BIT} in preparation for writing to an {@link android.media.AudioTrack}.
* A {@link BufferProcessor} that outputs buffers in {@link C#ENCODING_PCM_16BIT}.
*/
/* package */ final class ResamplingBufferProcessor implements BufferProcessor {
@C.PcmEncoding
private final int inputEncoding;
private int encoding;
private ByteBuffer outputBuffer;
/**
* Creates a new buffer processor for resampling input in the specified encoding.
*
* @param inputEncoding The PCM encoding of input buffers.
* @throws IllegalArgumentException Thrown if the input encoding is not PCM or its bit depth is
* not 8, 24 or 32-bits.
*/
public ResamplingBufferProcessor(@C.PcmEncoding int inputEncoding) {
Assertions.checkArgument(inputEncoding == C.ENCODING_PCM_8BIT
|| inputEncoding == C.ENCODING_PCM_24BIT || inputEncoding == C.ENCODING_PCM_32BIT);
this.inputEncoding = inputEncoding;
public ResamplingBufferProcessor() {
encoding = C.ENCODING_INVALID;
}
@Override
public ByteBuffer handleBuffer(ByteBuffer input, ByteBuffer output) {
int offset = input.position();
int limit = input.limit();
int size = limit - offset;
public void configure(int sampleRateHz, int channelCount, @C.Encoding int encoding)
throws UnhandledFormatException {
if (encoding != C.ENCODING_PCM_8BIT && encoding != C.ENCODING_PCM_16BIT
&& encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) {
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
}
if (encoding == C.ENCODING_PCM_16BIT) {
outputBuffer = null;
}
this.encoding = encoding;
}
@Override
public int getOutputEncoding() {
return C.ENCODING_PCM_16BIT;
}
@Override
public ByteBuffer handleBuffer(ByteBuffer buffer) {
int position = buffer.position();
int limit = buffer.limit();
int size = limit - position;
int resampledSize;
switch (inputEncoding) {
switch (encoding) {
case C.ENCODING_PCM_16BIT:
// No processing required.
return buffer;
case C.ENCODING_PCM_8BIT:
resampledSize = size * 2;
break;
@ -59,7 +71,6 @@ import java.nio.ByteBuffer;
case C.ENCODING_PCM_32BIT:
resampledSize = size / 2;
break;
case C.ENCODING_PCM_16BIT:
case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
@ -67,34 +78,34 @@ import java.nio.ByteBuffer;
throw new IllegalStateException();
}
ByteBuffer resampledBuffer = output;
if (resampledBuffer == null || resampledBuffer.capacity() < resampledSize) {
resampledBuffer = ByteBuffer.allocateDirect(resampledSize);
if (outputBuffer == null || outputBuffer.capacity() < resampledSize) {
outputBuffer = ByteBuffer.allocateDirect(resampledSize).order(buffer.order());
} else {
Assertions.checkState(!outputBuffer.hasRemaining());
outputBuffer.clear();
}
resampledBuffer.position(0);
resampledBuffer.limit(resampledSize);
// Samples are little endian.
switch (inputEncoding) {
switch (encoding) {
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) ((input.get(i) & 0xFF) - 128));
for (int i = position; i < limit; i++) {
outputBuffer.put((byte) 0);
outputBuffer.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(input.get(i + 1));
resampledBuffer.put(input.get(i + 2));
for (int i = position; i < limit; i += 3) {
outputBuffer.put(buffer.get(i + 1));
outputBuffer.put(buffer.get(i + 2));
}
break;
case C.ENCODING_PCM_32BIT:
// 32->16 bit resampling. Drop the two least significant bytes.
for (int i = offset; i < limit; i += 4) {
resampledBuffer.put(input.get(i + 2));
resampledBuffer.put(input.get(i + 3));
for (int i = position; i < limit; i += 4) {
outputBuffer.put(buffer.get(i + 2));
outputBuffer.put(buffer.get(i + 3));
}
break;
case C.ENCODING_PCM_16BIT:
@ -105,8 +116,18 @@ import java.nio.ByteBuffer;
throw new IllegalStateException();
}
resampledBuffer.position(0);
return resampledBuffer;
outputBuffer.flip();
return outputBuffer;
}
@Override
public void flush() {
// Do nothing.
}
@Override
public void release() {
outputBuffer = null;
}
}

View file

@ -102,10 +102,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
* @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 bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio buffers
* before they are output.
*/
public SimpleDecoderAudioRenderer(Handler eventHandler,
AudioRendererEventListener eventListener) {
this(eventHandler, eventListener, null);
AudioRendererEventListener eventListener, BufferProcessor... bufferProcessors) {
this(eventHandler, eventListener, null, null, false, bufferProcessors);
}
/**
@ -133,13 +135,16 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
* begin in parallel with key acquisition. 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 bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio
* buffers before they are output.
*/
public SimpleDecoderAudioRenderer(Handler eventHandler,
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys) {
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys,
BufferProcessor... bufferProcessors) {
super(C.TRACK_TYPE_AUDIO);
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
audioTrack = new AudioTrack(audioCapabilities, new AudioTrackListener());
audioTrack = new AudioTrack(audioCapabilities, bufferProcessors, new AudioTrackListener());
this.drmSessionManager = drmSessionManager;
formatHolder = new FormatHolder();
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
@ -193,8 +198,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
while (drainOutputBuffer()) {}
while (feedInputBuffer()) {}
TraceUtil.endSection();
} catch (AudioTrack.InitializationException | AudioTrack.WriteException
| AudioDecoderException e) {
} catch (AudioDecoderException | AudioTrack.ConfigurationException
| AudioTrack.InitializationException | AudioTrack.WriteException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
decoderCounters.ensureUpdated();
@ -255,7 +260,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
}
private boolean drainOutputBuffer() throws ExoPlaybackException, AudioDecoderException,
AudioTrack.InitializationException, AudioTrack.WriteException {
AudioTrack.ConfigurationException, AudioTrack.InitializationException,
AudioTrack.WriteException {
if (outputBuffer == null) {
outputBuffer = decoder.dequeueOutputBuffer();
if (outputBuffer == null) {

View file

@ -779,8 +779,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*
* @param codec The {@link MediaCodec} instance.
* @param outputFormat The new output format.
* @throws ExoPlaybackException Thrown if an error occurs handling the new output format.
*/
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) {
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat)
throws ExoPlaybackException {
// Do nothing.
}
@ -918,7 +920,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
/**
* Processes a new output format.
*/
private void processOutputFormat() {
private void processOutputFormat() throws ExoPlaybackException {
MediaFormat format = codec.getOutputFormat();
if (codecNeedsAdaptationWorkaround
&& format.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT