mirror of
https://github.com/androidx/media.git
synced 2025-05-07 15:40:37 +08:00
add support in mediacodecaudiorenderer for 24bit pcm to float
This commit is contained in:
parent
a1bac99f3b
commit
ca0c090c1a
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
|
Loading…
x
Reference in New Issue
Block a user