add support in mediacodecaudiorenderer for 24bit pcm to float

This commit is contained in:
Drew Hill 2017-12-23 13:25:45 -05:00
parent a1bac99f3b
commit ca0c090c1a
2 changed files with 268 additions and 11 deletions

View File

@ -0,0 +1,171 @@
package com.google.android.exoplayer2.audio;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* An {@link AudioProcessor} that converts audio data to {@link C#ENCODING_PCM_16BIT}.
*/
/* package */ final class FloatResamplingAudioProcessor implements AudioProcessor {
private int sampleRateHz;
private static final double PCM_INT32_FLOAT = 1.0 / 0x7fffffff;
private int channelCount;
@C.PcmEncoding
private int sourceEncoding;
private ByteBuffer buffer;
private ByteBuffer outputBuffer;
private boolean inputEnded;
/**
* Creates a new audio processor that converts audio data to {@link C#ENCODING_PCM_16BIT}.
*/
public FloatResamplingAudioProcessor() {
sampleRateHz = Format.NO_VALUE;
channelCount = Format.NO_VALUE;
sourceEncoding = C.ENCODING_INVALID;
buffer = EMPTY_BUFFER;
outputBuffer = EMPTY_BUFFER;
}
@Override
public boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding)
throws AudioProcessor.UnhandledFormatException {
if (encoding != C.ENCODING_PCM_24BIT) {
throw new AudioProcessor.UnhandledFormatException(sampleRateHz, channelCount, encoding);
}
if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount
&& this.sourceEncoding == encoding) {
return false;
}
this.sampleRateHz = sampleRateHz;
this.channelCount = channelCount;
this.sourceEncoding = encoding;
return true;
}
@Override
public boolean isActive() { return sourceEncoding == C.ENCODING_PCM_24BIT; }
@Override
public int getOutputChannelCount() { return channelCount; }
@Override
public int getOutputEncoding() { return C.ENCODING_PCM_FLOAT; }
@Override
public int getOutputSampleRateHz() {
return sampleRateHz;
}
@Override
public void queueInput(ByteBuffer inputBuffer) {
int offset = inputBuffer.position();
int limit = inputBuffer.limit();
int size = limit - offset;
int resampledSize;
switch (sourceEncoding) {
case C.ENCODING_PCM_24BIT:
resampledSize = (size / 3) * 4;
break;
case C.ENCODING_PCM_32BIT:
case C.ENCODING_PCM_8BIT:
case C.ENCODING_PCM_16BIT:
case C.ENCODING_PCM_FLOAT:
case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
// Never happens.
throw new IllegalStateException();
}
if (buffer.capacity() < resampledSize) {
buffer = ByteBuffer.allocateDirect(resampledSize).order(ByteOrder.nativeOrder());
} else {
buffer.clear();
}
// Samples are little endian.
switch (sourceEncoding) {
case C.ENCODING_PCM_24BIT:
// 24->32 bit resampling.
for (int i = offset; i < limit; i += 3) {
int val = (inputBuffer.get(i) << 8) & 0x0000ff00 | (inputBuffer.get(i + 1) << 16) & 0x00ff0000 |
(inputBuffer.get(i + 2) << 24) & 0xff000000;
writePcm32bitFloat(val, buffer);
}
break;
case C.ENCODING_PCM_32BIT:
case C.ENCODING_PCM_8BIT:
case C.ENCODING_PCM_16BIT:
case C.ENCODING_PCM_FLOAT:
case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
// Never happens.
throw new IllegalStateException();
}
inputBuffer.position(inputBuffer.limit());
buffer.flip();
outputBuffer = buffer;
}
@Override
public void queueEndOfStream() {
inputEnded = true;
}
@Override
public ByteBuffer getOutput() {
ByteBuffer outputBuffer = this.outputBuffer;
this.outputBuffer = EMPTY_BUFFER;
return outputBuffer;
}
@SuppressWarnings("ReferenceEquality")
@Override
public boolean isEnded() {
return inputEnded && outputBuffer == EMPTY_BUFFER;
}
@Override
public void flush() {
outputBuffer = EMPTY_BUFFER;
inputEnded = false;
}
@Override
public void reset() {
flush();
buffer = EMPTY_BUFFER;
sampleRateHz = Format.NO_VALUE;
channelCount = Format.NO_VALUE;
sourceEncoding = C.ENCODING_INVALID;
}
/**
* Converts the provided value into 32-bit float PCM and writes to buffer.
*
* @param val 32-bit int value to convert to 32-bit float [-1.0, 1.0]
* @param buffer The output buffer.
*/
private static void writePcm32bitFloat(int val, ByteBuffer buffer) {
float convVal = (float) (PCM_INT32_FLOAT * val);
int bits = Float.floatToIntBits(convVal);
if (bits == 0x7fc00000)
bits = Float.floatToIntBits((float) 0.0);
buffer.put((byte) (bits & 0xff));
buffer.put((byte) ((bits >> 8) & 0xff));
buffer.put((byte) ((bits >> 16) & 0xff));
buffer.put((byte) ((bits >> 24) & 0xff));
}
}

View File

@ -58,6 +58,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
private int encoderPadding;
private long currentPositionUs;
private boolean allowPositionDiscontinuity;
private final boolean dontDither24bitPCM;
private ByteBuffer resampledBuffer;
private FloatResamplingAudioProcessor floatResamplingAudioProcessor;
/**
* @param mediaCodecSelector A decoder selector.
@ -137,7 +140,37 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Nullable AudioRendererEventListener eventListener,
@Nullable AudioCapabilities audioCapabilities, AudioProcessor... audioProcessors) {
this(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys,
eventHandler, eventListener, new DefaultAudioSink(audioCapabilities, audioProcessors));
eventHandler, eventListener, new DefaultAudioSink(audioCapabilities, audioProcessors),
false);
}
/**
* @param mediaCodecSelector A decoder selector.
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
* content is not required.
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
* For example a media file may start with a short clear region so as to allow playback to
* 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 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 dontDither24bitPCM If the input is 24bit PCM audio convert to 32bit Float PCM
* @param audioProcessors Optional {@link AudioProcessor}s that will process PCM audio before
* output.
*/
public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys, @Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener,
@Nullable AudioCapabilities audioCapabilities, boolean dontDither24bitPCM,
AudioProcessor... audioProcessors) {
this(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys,
eventHandler, eventListener, new DefaultAudioSink(audioCapabilities, audioProcessors),
dontDither24bitPCM);
}
/**
@ -158,9 +191,34 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys, @Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener, AudioSink audioSink) {
this(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, eventHandler,
eventListener, audioSink, false);
}
/**
* @param mediaCodecSelector A decoder selector.
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
* content is not required.
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
* For example a media file may start with a short clear region so as to allow playback to
* 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 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 audioSink The sink to which audio will be output.
* @param dontDither24bitPCM If the input is 24bit PCM audio convert to 32bit Float PCM
*/
public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys, @Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener, AudioSink audioSink,
boolean dontDither24bitPCM) {
super(C.TRACK_TYPE_AUDIO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys);
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
this.audioSink = audioSink;
this.dontDither24bitPCM = dontDither24bitPCM;
audioSink.setListener(new AudioSinkListener());
}
@ -268,10 +326,20 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
super.onInputFormatChanged(newFormat);
eventDispatcher.inputFormatChanged(newFormat);
// If the input format is anything other than PCM then we assume that the audio decoder will
// output 16-bit PCM.
pcmEncoding = MimeTypes.AUDIO_RAW.equals(newFormat.sampleMimeType) ? newFormat.pcmEncoding
: C.ENCODING_PCM_16BIT;
// if the input is 24bit pcm audio and we explicitly said not to dither then convert it to float
if (dontDither24bitPCM && newFormat.pcmEncoding == C.ENCODING_PCM_24BIT) {
if (floatResamplingAudioProcessor == null)
floatResamplingAudioProcessor = new FloatResamplingAudioProcessor();
pcmEncoding = floatResamplingAudioProcessor.getOutputEncoding();
} else {
// If the input format is anything other than PCM then we assume that the audio decoder will
// output 16-bit PCM.
pcmEncoding = MimeTypes.AUDIO_RAW.equals(newFormat.sampleMimeType) ? newFormat.pcmEncoding
: C.ENCODING_PCM_16BIT;
floatResamplingAudioProcessor = null;
}
channelCount = newFormat.channelCount;
encoderDelay = newFormat.encoderDelay != Format.NO_VALUE ? newFormat.encoderDelay : 0;
encoderPadding = newFormat.encoderPadding != Format.NO_VALUE ? newFormat.encoderPadding : 0;
@ -302,9 +370,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
try {
if (floatResamplingAudioProcessor != null)
floatResamplingAudioProcessor.configure(sampleRate, channelCount, C.ENCODING_PCM_24BIT);
audioSink.configure(encoding, channelCount, sampleRate, 0, channelMap, encoderDelay,
encoderPadding);
} catch (AudioSink.ConfigurationException e) {
} catch (AudioSink.ConfigurationException | AudioProcessor.UnhandledFormatException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
}
@ -420,19 +490,35 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
codec.releaseOutputBuffer(bufferIndex, false);
return true;
}
if (shouldSkip) {
codec.releaseOutputBuffer(bufferIndex, false);
decoderCounters.skippedOutputBufferCount++;
audioSink.handleDiscontinuity();
resampledBuffer = null;
return true;
}
try {
if (audioSink.handleBuffer(buffer, bufferPresentationTimeUs)) {
codec.releaseOutputBuffer(bufferIndex, false);
decoderCounters.renderedOutputBufferCount++;
return true;
if (floatResamplingAudioProcessor != null) {
boolean draining = resampledBuffer != null;
if (!draining) {
floatResamplingAudioProcessor.queueInput(buffer);
resampledBuffer = floatResamplingAudioProcessor.getOutput();
}
if (audioSink.handleBuffer(resampledBuffer, bufferPresentationTimeUs))
resampledBuffer = null;
if (!draining) {
codec.releaseOutputBuffer(bufferIndex, false);
decoderCounters.renderedOutputBufferCount++;
return true;
}
}
else {
if (audioSink.handleBuffer(buffer, bufferPresentationTimeUs)) {
codec.releaseOutputBuffer(bufferIndex, false);
decoderCounters.renderedOutputBufferCount++;
return true;
}
}
} catch (AudioSink.InitializationException | AudioSink.WriteException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());