mirror of
https://github.com/samsonjs/media.git
synced 2026-04-07 11:35:46 +00:00
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:
parent
feeec77407
commit
74acbe04e3
10 changed files with 238 additions and 147 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue