Commit 74acbe04 by andrewlewis Committed by Oliver Woodman

Pass an array of BufferProcessors to the AudioTrack.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=146215966
parent feeec774
...@@ -19,8 +19,8 @@ import android.os.Handler; ...@@ -19,8 +19,8 @@ import android.os.Handler;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; 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.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer; import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
...@@ -43,21 +43,12 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -43,21 +43,12 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * 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 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
public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener) { * before they are output.
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.
*/ */
public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener, public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities) { BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, audioCapabilities); super(eventHandler, eventListener, bufferProcessors);
} }
@Override @Override
......
...@@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.flac; ...@@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.flac;
import android.os.Handler; import android.os.Handler;
import com.google.android.exoplayer2.Format; 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.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer; import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
...@@ -38,21 +38,12 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -38,21 +38,12 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * 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 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
public LibflacAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener) { * before they are output.
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.
*/ */
public LibflacAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener, public LibflacAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities) { BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, audioCapabilities); super(eventHandler, eventListener, bufferProcessors);
} }
@Override @Override
......
...@@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.opus; ...@@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.opus;
import android.os.Handler; import android.os.Handler;
import com.google.android.exoplayer2.Format; 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.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer; import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.drm.ExoMediaCrypto;
...@@ -40,35 +40,26 @@ public final class LibopusAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -40,35 +40,26 @@ public final class LibopusAudioRenderer extends SimpleDecoderAudioRenderer {
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * 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 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
public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener) { * before they are output.
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.
*/ */
public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener, public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities) { BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, audioCapabilities); super(eventHandler, eventListener, bufferProcessors);
} }
/** /**
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * 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 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 * @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio
* default capabilities (no encoded audio passthrough support) should be assumed. * buffers before they are output.
*/ */
public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener, public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities, DrmSessionManager<ExoMediaCrypto> drmSessionManager, DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys,
boolean playClearSamplesWithoutKeys) { BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, audioCapabilities, drmSessionManager, super(eventHandler, eventListener, null, drmSessionManager, playClearSamplesWithoutKeys,
playClearSamplesWithoutKeys); bufferProcessors);
} }
@Override @Override
......
...@@ -29,6 +29,7 @@ import android.view.SurfaceView; ...@@ -29,6 +29,7 @@ import android.view.SurfaceView;
import android.view.TextureView; import android.view.TextureView;
import com.google.android.exoplayer2.audio.AudioCapabilities; import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener; 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.audio.MediaCodecAudioRenderer;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
...@@ -624,7 +625,7 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -624,7 +625,7 @@ public class SimpleExoPlayer implements ExoPlayer {
buildVideoRenderers(context, mainHandler, drmSessionManager, extensionRendererMode, buildVideoRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
componentListener, allowedVideoJoiningTimeMs, out); componentListener, allowedVideoJoiningTimeMs, out);
buildAudioRenderers(context, mainHandler, drmSessionManager, extensionRendererMode, buildAudioRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
componentListener, out); componentListener, buildBufferProcessors(), out);
buildTextRenderers(context, mainHandler, extensionRendererMode, componentListener, out); buildTextRenderers(context, mainHandler, extensionRendererMode, componentListener, out);
buildMetadataRenderers(context, mainHandler, extensionRendererMode, componentListener, out); buildMetadataRenderers(context, mainHandler, extensionRendererMode, componentListener, out);
buildMiscellaneousRenderers(context, mainHandler, extensionRendererMode, out); buildMiscellaneousRenderers(context, mainHandler, extensionRendererMode, out);
...@@ -684,14 +685,16 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -684,14 +685,16 @@ public class SimpleExoPlayer implements ExoPlayer {
* not be used for DRM protected playbacks. * not be used for DRM protected playbacks.
* @param extensionRendererMode The extension renderer mode. * @param extensionRendererMode The extension renderer mode.
* @param eventListener An event listener. * @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. * @param out An array to which the built renderers should be appended.
*/ */
protected void buildAudioRenderers(Context context, Handler mainHandler, protected void buildAudioRenderers(Context context, Handler mainHandler,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, AudioRendererEventListener eventListener, @ExtensionRendererMode int extensionRendererMode, AudioRendererEventListener eventListener,
ArrayList<Renderer> out) { BufferProcessor[] bufferProcessors, ArrayList<Renderer> out) {
out.add(new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, drmSessionManager, true, 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) { if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
return; return;
...@@ -705,8 +708,9 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -705,8 +708,9 @@ public class SimpleExoPlayer implements ExoPlayer {
Class<?> clazz = Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer"); Class.forName("com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class, Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class); AudioRendererEventListener.class, BufferProcessor[].class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener); Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener,
bufferProcessors);
out.add(extensionRendererIndex++, renderer); out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibopusAudioRenderer."); Log.i(TAG, "Loaded LibopusAudioRenderer.");
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
...@@ -719,8 +723,9 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -719,8 +723,9 @@ public class SimpleExoPlayer implements ExoPlayer {
Class<?> clazz = Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer"); Class.forName("com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class, Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class); AudioRendererEventListener.class, BufferProcessor[].class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener); Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener,
bufferProcessors);
out.add(extensionRendererIndex++, renderer); out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibflacAudioRenderer."); Log.i(TAG, "Loaded LibflacAudioRenderer.");
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
...@@ -733,8 +738,9 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -733,8 +738,9 @@ public class SimpleExoPlayer implements ExoPlayer {
Class<?> clazz = Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer"); Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class, Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class); AudioRendererEventListener.class, BufferProcessor[].class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener); Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener,
bufferProcessors);
out.add(extensionRendererIndex++, renderer); out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded FfmpegAudioRenderer."); Log.i(TAG, "Loaded FfmpegAudioRenderer.");
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
...@@ -787,6 +793,14 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -787,6 +793,14 @@ public class SimpleExoPlayer implements ExoPlayer {
// Do nothing. // 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. // Internal methods.
private void removeSurfaceCallbacks() { private void removeSurfaceCallbacks() {
......
...@@ -91,6 +91,21 @@ public final class AudioTrack { ...@@ -91,6 +91,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}. * Thrown when a failure occurs initializing an {@link android.media.AudioTrack}.
*/ */
public static final class InitializationException extends Exception { public static final class InitializationException extends Exception {
...@@ -254,6 +269,7 @@ public final class AudioTrack { ...@@ -254,6 +269,7 @@ public final class AudioTrack {
public static boolean failOnSpuriousAudioTimestamp = false; public static boolean failOnSpuriousAudioTimestamp = false;
private final AudioCapabilities audioCapabilities; private final AudioCapabilities audioCapabilities;
private final BufferProcessor[] bufferProcessors;
private final Listener listener; private final Listener listener;
private final ConditionVariable releasingConditionVariable; private final ConditionVariable releasingConditionVariable;
private final long[] playheadOffsets; private final long[] playheadOffsets;
...@@ -267,12 +283,12 @@ public final class AudioTrack { ...@@ -267,12 +283,12 @@ 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;
@C.StreamType
private int streamType;
@C.Encoding @C.Encoding
private int inputEncoding; private int encoding;
@C.Encoding @C.Encoding
private int outputEncoding; private int outputEncoding;
@C.StreamType
private int streamType;
private boolean passthrough; private boolean passthrough;
private int pcmFrameSize; private int pcmFrameSize;
private int bufferSize; private int bufferSize;
...@@ -303,8 +319,6 @@ public final class AudioTrack { ...@@ -303,8 +319,6 @@ public final class AudioTrack {
private byte[] preV21OutputBuffer; private byte[] preV21OutputBuffer;
private int preV21OutputBufferOffset; private int preV21OutputBufferOffset;
private BufferProcessor resampler;
private boolean playing; private boolean playing;
private int audioSessionId; private int audioSessionId;
private boolean tunneling; private boolean tunneling;
...@@ -312,11 +326,18 @@ public final class AudioTrack { ...@@ -312,11 +326,18 @@ public final class AudioTrack {
private long lastFeedElapsedRealtimeMs; 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. * @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.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; this.listener = listener;
releasingConditionVariable = new ConditionVariable(true); releasingConditionVariable = new ConditionVariable(true);
if (Util.SDK_INT >= 18) { if (Util.SDK_INT >= 18) {
...@@ -413,9 +434,23 @@ public final class AudioTrack { ...@@ -413,9 +434,23 @@ public final class AudioTrack {
* {@link C#ENCODING_PCM_32BIT}. * {@link C#ENCODING_PCM_32BIT}.
* @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.
* @throws ConfigurationException If an error occurs configuring the track.
*/ */
public void configure(String mimeType, int channelCount, int sampleRate, 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; int channelConfig;
switch (channelCount) { switch (channelCount) {
case 1: case 1:
...@@ -443,7 +478,7 @@ public final class AudioTrack { ...@@ -443,7 +478,7 @@ public final class AudioTrack {
channelConfig = C.CHANNEL_OUT_7POINT1_SURROUND; channelConfig = C.CHANNEL_OUT_7POINT1_SURROUND;
break; break;
default: 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. // Workaround for overly strict channel configuration checks on nVidia Shield.
...@@ -461,25 +496,13 @@ public final class AudioTrack { ...@@ -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. // Workaround for Nexus Player not reporting support for mono passthrough.
// (See [Internal: b/34268671].) // (See [Internal: b/34268671].)
if (Util.SDK_INT <= 25 && "fugu".equals(Util.DEVICE) && passthrough && channelCount == 1) { if (Util.SDK_INT <= 25 && "fugu".equals(Util.DEVICE) && passthrough && channelCount == 1) {
channelConfig = AudioFormat.CHANNEL_OUT_STEREO; channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
} }
@C.Encoding int inputEncoding; if (isInitialized() && this.encoding == encoding && this.sampleRate == sampleRate
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
&& this.channelConfig == channelConfig) { && this.channelConfig == channelConfig) {
// We already have an audio track with the correct sample rate, channel config and encoding. // We already have an audio track with the correct sample rate, channel config and encoding.
return; return;
...@@ -487,15 +510,12 @@ public final class AudioTrack { ...@@ -487,15 +510,12 @@ public final class AudioTrack {
reset(); reset();
this.inputEncoding = inputEncoding; this.encoding = encoding;
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. pcmFrameSize = 2 * channelCount; // 2 bytes per 16-bit sample * number of channels.
outputEncoding = passthrough ? inputEncoding : C.ENCODING_PCM_16BIT; outputEncoding = passthrough ? encoding : C.ENCODING_PCM_16BIT;
resampler = outputEncoding != inputEncoding ? new ResamplingBufferProcessor(inputEncoding)
: null;
if (specifiedBufferSize != 0) { if (specifiedBufferSize != 0) {
bufferSize = specifiedBufferSize; bufferSize = specifiedBufferSize;
...@@ -684,8 +704,12 @@ public final class AudioTrack { ...@@ -684,8 +704,12 @@ public final class AudioTrack {
} }
inputBuffer = buffer; inputBuffer = buffer;
outputBuffer = resampler != null ? resampler.handleBuffer(inputBuffer, outputBuffer) if (!passthrough) {
: inputBuffer; for (BufferProcessor bufferProcessor : bufferProcessors) {
buffer = bufferProcessor.handleBuffer(buffer);
}
}
outputBuffer = buffer;
if (Util.SDK_INT < 21) { if (Util.SDK_INT < 21) {
int bytesRemaining = outputBuffer.remaining(); int bytesRemaining = outputBuffer.remaining();
if (preV21OutputBuffer == null || preV21OutputBuffer.length < bytesRemaining) { if (preV21OutputBuffer == null || preV21OutputBuffer.length < bytesRemaining) {
...@@ -886,6 +910,9 @@ public final class AudioTrack { ...@@ -886,6 +910,9 @@ public final class AudioTrack {
framesPerEncodedSample = 0; framesPerEncodedSample = 0;
inputBuffer = null; inputBuffer = null;
avSyncHeader = null; avSyncHeader = null;
for (BufferProcessor bufferProcessor : bufferProcessors) {
bufferProcessor.flush();
}
bytesUntilNextAvSync = 0; bytesUntilNextAvSync = 0;
startMediaTimeState = START_NOT_SET; startMediaTimeState = START_NOT_SET;
latencyUs = 0; latencyUs = 0;
...@@ -919,6 +946,9 @@ public final class AudioTrack { ...@@ -919,6 +946,9 @@ public final class AudioTrack {
public void release() { public void release() {
reset(); reset();
releaseKeepSessionIdAudioTrack(); releaseKeepSessionIdAudioTrack();
for (BufferProcessor bufferProcessor : bufferProcessors) {
bufferProcessor.release();
}
audioSessionId = C.AUDIO_SESSION_ID_UNSET; audioSessionId = C.AUDIO_SESSION_ID_UNSET;
playing = false; playing = false;
} }
......
...@@ -15,23 +15,60 @@ ...@@ -15,23 +15,60 @@
*/ */
package com.google.android.exoplayer2.audio; package com.google.android.exoplayer2.audio;
import com.google.android.exoplayer2.C;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
* Interface for processors of buffers, for use with {@link AudioTrack}. * Interface for processors of audio buffers.
*/ */
public interface BufferProcessor { public interface BufferProcessor {
/** /**
* Processes the data in the specified input buffer in its entirety. Populates {@code output} with * Exception thrown when a processor can't be configured for a given input format.
* processed data if is not {@code null} and has sufficient capacity. Otherwise a different buffer */
* will be populated and returned. 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 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 A buffer containing the processed output. This may be the same as the input buffer if
* @return The processed output. Different to {@code output} if null was passed, or if its * no processing was required.
* capacity was insufficient. */
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.
*/ */
ByteBuffer handleBuffer(ByteBuffer input, ByteBuffer output); void release();
} }
...@@ -121,13 +121,16 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -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 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 * @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. * 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, public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys, Handler eventHandler, boolean playClearSamplesWithoutKeys, Handler eventHandler,
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities) { AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
BufferProcessor... bufferProcessors) {
super(C.TRACK_TYPE_AUDIO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys); 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); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
} }
...@@ -219,14 +222,19 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -219,14 +222,19 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
@Override @Override
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) { protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat)
throws ExoPlaybackException {
boolean passthrough = passthroughMediaFormat != null; boolean passthrough = passthroughMediaFormat != null;
String mimeType = passthrough ? passthroughMediaFormat.getString(MediaFormat.KEY_MIME) String mimeType = passthrough ? passthroughMediaFormat.getString(MediaFormat.KEY_MIME)
: MimeTypes.AUDIO_RAW; : MimeTypes.AUDIO_RAW;
MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat; MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat;
int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
try {
audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0); audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0);
} catch (AudioTrack.ConfigurationException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
} }
/** /**
......
...@@ -21,35 +21,47 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -21,35 +21,47 @@ import com.google.android.exoplayer2.util.Assertions;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
* A {@link BufferProcessor} that converts PCM input buffers from a specified input bit depth to * A {@link BufferProcessor} that outputs buffers in {@link C#ENCODING_PCM_16BIT}.
* {@link C#ENCODING_PCM_16BIT} in preparation for writing to an {@link android.media.AudioTrack}.
*/ */
/* package */ final class ResamplingBufferProcessor implements BufferProcessor { /* package */ final class ResamplingBufferProcessor implements BufferProcessor {
@C.PcmEncoding @C.PcmEncoding
private final int inputEncoding; private int encoding;
private ByteBuffer outputBuffer;
/** public ResamplingBufferProcessor() {
* Creates a new buffer processor for resampling input in the specified encoding. encoding = C.ENCODING_INVALID;
* }
* @param inputEncoding The PCM encoding of input buffers.
* @throws IllegalArgumentException Thrown if the input encoding is not PCM or its bit depth is @Override
* not 8, 24 or 32-bits. public void configure(int sampleRateHz, int channelCount, @C.Encoding int encoding)
*/ throws UnhandledFormatException {
public ResamplingBufferProcessor(@C.PcmEncoding int inputEncoding) { if (encoding != C.ENCODING_PCM_8BIT && encoding != C.ENCODING_PCM_16BIT
Assertions.checkArgument(inputEncoding == C.ENCODING_PCM_8BIT && encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) {
|| inputEncoding == C.ENCODING_PCM_24BIT || inputEncoding == C.ENCODING_PCM_32BIT); throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
this.inputEncoding = inputEncoding; }
if (encoding == C.ENCODING_PCM_16BIT) {
outputBuffer = null;
}
this.encoding = encoding;
}
@Override
public int getOutputEncoding() {
return C.ENCODING_PCM_16BIT;
} }
@Override @Override
public ByteBuffer handleBuffer(ByteBuffer input, ByteBuffer output) { public ByteBuffer handleBuffer(ByteBuffer buffer) {
int offset = input.position(); int position = buffer.position();
int limit = input.limit(); int limit = buffer.limit();
int size = limit - offset; int size = limit - position;
int resampledSize; int resampledSize;
switch (inputEncoding) { switch (encoding) {
case C.ENCODING_PCM_16BIT:
// No processing required.
return buffer;
case C.ENCODING_PCM_8BIT: case C.ENCODING_PCM_8BIT:
resampledSize = size * 2; resampledSize = size * 2;
break; break;
...@@ -59,7 +71,6 @@ import java.nio.ByteBuffer; ...@@ -59,7 +71,6 @@ import java.nio.ByteBuffer;
case C.ENCODING_PCM_32BIT: case C.ENCODING_PCM_32BIT:
resampledSize = size / 2; resampledSize = size / 2;
break; break;
case C.ENCODING_PCM_16BIT:
case C.ENCODING_INVALID: case C.ENCODING_INVALID:
case Format.NO_VALUE: case Format.NO_VALUE:
default: default:
...@@ -67,34 +78,34 @@ import java.nio.ByteBuffer; ...@@ -67,34 +78,34 @@ import java.nio.ByteBuffer;
throw new IllegalStateException(); throw new IllegalStateException();
} }
ByteBuffer resampledBuffer = output; if (outputBuffer == null || outputBuffer.capacity() < resampledSize) {
if (resampledBuffer == null || resampledBuffer.capacity() < resampledSize) { outputBuffer = ByteBuffer.allocateDirect(resampledSize).order(buffer.order());
resampledBuffer = ByteBuffer.allocateDirect(resampledSize); } else {
Assertions.checkState(!outputBuffer.hasRemaining());
outputBuffer.clear();
} }
resampledBuffer.position(0);
resampledBuffer.limit(resampledSize);
// Samples are little endian. // Samples are little endian.
switch (inputEncoding) { switch (encoding) {
case C.ENCODING_PCM_8BIT: case C.ENCODING_PCM_8BIT:
// 8->16 bit resampling. Shift each byte from [0, 256) to [-128, 128) and scale up. // 8->16 bit resampling. Shift each byte from [0, 256) to [-128, 128) and scale up.
for (int i = offset; i < limit; i++) { for (int i = position; i < limit; i++) {
resampledBuffer.put((byte) 0); outputBuffer.put((byte) 0);
resampledBuffer.put((byte) ((input.get(i) & 0xFF) - 128)); outputBuffer.put((byte) ((buffer.get(i) & 0xFF) - 128));
} }
break; break;
case C.ENCODING_PCM_24BIT: case C.ENCODING_PCM_24BIT:
// 24->16 bit resampling. Drop the least significant byte. // 24->16 bit resampling. Drop the least significant byte.
for (int i = offset; i < limit; i += 3) { for (int i = position; i < limit; i += 3) {
resampledBuffer.put(input.get(i + 1)); outputBuffer.put(buffer.get(i + 1));
resampledBuffer.put(input.get(i + 2)); outputBuffer.put(buffer.get(i + 2));
} }
break; break;
case C.ENCODING_PCM_32BIT: case C.ENCODING_PCM_32BIT:
// 32->16 bit resampling. Drop the two least significant bytes. // 32->16 bit resampling. Drop the two least significant bytes.
for (int i = offset; i < limit; i += 4) { for (int i = position; i < limit; i += 4) {
resampledBuffer.put(input.get(i + 2)); outputBuffer.put(buffer.get(i + 2));
resampledBuffer.put(input.get(i + 3)); outputBuffer.put(buffer.get(i + 3));
} }
break; break;
case C.ENCODING_PCM_16BIT: case C.ENCODING_PCM_16BIT:
...@@ -105,8 +116,18 @@ import java.nio.ByteBuffer; ...@@ -105,8 +116,18 @@ import java.nio.ByteBuffer;
throw new IllegalStateException(); throw new IllegalStateException();
} }
resampledBuffer.position(0); outputBuffer.flip();
return resampledBuffer; return outputBuffer;
}
@Override
public void flush() {
// Do nothing.
}
@Override
public void release() {
outputBuffer = null;
} }
} }
...@@ -102,10 +102,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -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 * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * 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 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, public SimpleDecoderAudioRenderer(Handler eventHandler,
AudioRendererEventListener eventListener) { AudioRendererEventListener eventListener, BufferProcessor... bufferProcessors) {
this(eventHandler, eventListener, null); this(eventHandler, eventListener, null, null, false, bufferProcessors);
} }
/** /**
...@@ -133,13 +135,16 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -133,13 +135,16 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
* begin in parallel with key acquisition. This parameter specifies whether the renderer is * 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} * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
* has obtained the keys necessary to decrypt encrypted regions of the media. * 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, public SimpleDecoderAudioRenderer(Handler eventHandler,
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities, AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys) { DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys,
BufferProcessor... bufferProcessors) {
super(C.TRACK_TYPE_AUDIO); super(C.TRACK_TYPE_AUDIO);
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
audioTrack = new AudioTrack(audioCapabilities, new AudioTrackListener()); audioTrack = new AudioTrack(audioCapabilities, bufferProcessors, new AudioTrackListener());
this.drmSessionManager = drmSessionManager; this.drmSessionManager = drmSessionManager;
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
...@@ -193,8 +198,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -193,8 +198,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
while (drainOutputBuffer()) {} while (drainOutputBuffer()) {}
while (feedInputBuffer()) {} while (feedInputBuffer()) {}
TraceUtil.endSection(); TraceUtil.endSection();
} catch (AudioTrack.InitializationException | AudioTrack.WriteException } catch (AudioDecoderException | AudioTrack.ConfigurationException
| AudioDecoderException e) { | AudioTrack.InitializationException | AudioTrack.WriteException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
} }
decoderCounters.ensureUpdated(); decoderCounters.ensureUpdated();
...@@ -255,7 +260,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -255,7 +260,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
private boolean drainOutputBuffer() throws ExoPlaybackException, AudioDecoderException, private boolean drainOutputBuffer() throws ExoPlaybackException, AudioDecoderException,
AudioTrack.InitializationException, AudioTrack.WriteException { AudioTrack.ConfigurationException, AudioTrack.InitializationException,
AudioTrack.WriteException {
if (outputBuffer == null) { if (outputBuffer == null) {
outputBuffer = decoder.dequeueOutputBuffer(); outputBuffer = decoder.dequeueOutputBuffer();
if (outputBuffer == null) { if (outputBuffer == null) {
......
...@@ -779,8 +779,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -779,8 +779,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* *
* @param codec The {@link MediaCodec} instance. * @param codec The {@link MediaCodec} instance.
* @param outputFormat The new output format. * @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. // Do nothing.
} }
...@@ -918,7 +920,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -918,7 +920,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
/** /**
* Processes a new output format. * Processes a new output format.
*/ */
private void processOutputFormat() { private void processOutputFormat() throws ExoPlaybackException {
MediaFormat format = codec.getOutputFormat(); MediaFormat format = codec.getOutputFormat();
if (codecNeedsAdaptationWorkaround if (codecNeedsAdaptationWorkaround
&& format.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT && format.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment