mirror of
https://github.com/samsonjs/media.git
synced 2026-04-01 10:35:48 +00:00
Merge pull request #3635 from drhill/dev-v2_24bitpcm
add support in mediacodecaudiorenderer for 24bit pcm to float
This commit is contained in:
commit
4d2e0bf122
2 changed files with 237 additions and 9 deletions
|
|
@ -164,10 +164,12 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
public static boolean failOnSpuriousAudioTimestamp = false;
|
||||
|
||||
@Nullable private final AudioCapabilities audioCapabilities;
|
||||
private final boolean canConvertHiResPcmToFloat;
|
||||
private final ChannelMappingAudioProcessor channelMappingAudioProcessor;
|
||||
private final TrimmingAudioProcessor trimmingAudioProcessor;
|
||||
private final SonicAudioProcessor sonicAudioProcessor;
|
||||
private final AudioProcessor[] availableAudioProcessors;
|
||||
private final AudioProcessor[] toIntPcmAvailableAudioProcessors;
|
||||
private final AudioProcessor[] toFloatPcmAvailableAudioProcessors;
|
||||
private final ConditionVariable releasingConditionVariable;
|
||||
private final long[] playheadOffsets;
|
||||
private final AudioTrackUtil audioTrackUtil;
|
||||
|
|
@ -180,12 +182,14 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
private AudioTrack keepSessionIdAudioTrack;
|
||||
private AudioTrack audioTrack;
|
||||
private boolean isInputPcm;
|
||||
private boolean shouldUpResPCMAudio;
|
||||
private int inputSampleRate;
|
||||
private int sampleRate;
|
||||
private int channelConfig;
|
||||
private @C.Encoding int outputEncoding;
|
||||
private AudioAttributes audioAttributes;
|
||||
private boolean processingEnabled;
|
||||
private boolean canApplyPlaybackParams;
|
||||
private int bufferSize;
|
||||
private long bufferSizeUs;
|
||||
|
||||
|
|
@ -233,6 +237,8 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
private boolean hasData;
|
||||
private long lastFeedElapsedRealtimeMs;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @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.
|
||||
|
|
@ -241,7 +247,23 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
*/
|
||||
public DefaultAudioSink(@Nullable AudioCapabilities audioCapabilities,
|
||||
AudioProcessor[] audioProcessors) {
|
||||
this(audioCapabilities, audioProcessors, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 audioProcessors An array of {@link AudioProcessor}s that will process PCM audio before
|
||||
* output. May be empty.
|
||||
* @param canConvertHiResPcmToFloat Flag to convert > 16bit PCM Audio to 32bit Float PCM Audio to
|
||||
* avoid dithering the input audio. If enabled other audio processors that expect 16bit PCM
|
||||
* are disabled
|
||||
*/
|
||||
public DefaultAudioSink(@Nullable AudioCapabilities audioCapabilities,
|
||||
AudioProcessor[] audioProcessors, boolean canConvertHiResPcmToFloat) {
|
||||
|
||||
this.audioCapabilities = audioCapabilities;
|
||||
this.canConvertHiResPcmToFloat = canConvertHiResPcmToFloat;
|
||||
releasingConditionVariable = new ConditionVariable(true);
|
||||
if (Util.SDK_INT >= 18) {
|
||||
try {
|
||||
|
|
@ -259,12 +281,14 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
channelMappingAudioProcessor = new ChannelMappingAudioProcessor();
|
||||
trimmingAudioProcessor = new TrimmingAudioProcessor();
|
||||
sonicAudioProcessor = new SonicAudioProcessor();
|
||||
availableAudioProcessors = new AudioProcessor[4 + audioProcessors.length];
|
||||
availableAudioProcessors[0] = new ResamplingAudioProcessor();
|
||||
availableAudioProcessors[1] = channelMappingAudioProcessor;
|
||||
availableAudioProcessors[2] = trimmingAudioProcessor;
|
||||
System.arraycopy(audioProcessors, 0, availableAudioProcessors, 3, audioProcessors.length);
|
||||
availableAudioProcessors[3 + audioProcessors.length] = sonicAudioProcessor;
|
||||
toIntPcmAvailableAudioProcessors = new AudioProcessor[4 + audioProcessors.length];
|
||||
toIntPcmAvailableAudioProcessors[0] = new ResamplingAudioProcessor();
|
||||
toIntPcmAvailableAudioProcessors[1] = channelMappingAudioProcessor;
|
||||
toIntPcmAvailableAudioProcessors[2] = trimmingAudioProcessor;
|
||||
System.arraycopy(audioProcessors, 0, toIntPcmAvailableAudioProcessors, 3, audioProcessors.length);
|
||||
toIntPcmAvailableAudioProcessors[3 + audioProcessors.length] = sonicAudioProcessor;
|
||||
toFloatPcmAvailableAudioProcessors = new AudioProcessor[1];
|
||||
toFloatPcmAvailableAudioProcessors[0] = new FloatResamplingAudioProcessor();
|
||||
playheadOffsets = new long[MAX_PLAYHEAD_OFFSET_COUNT];
|
||||
volume = 1.0f;
|
||||
startMediaTimeState = START_NOT_SET;
|
||||
|
|
@ -342,12 +366,17 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
int channelCount = inputChannelCount;
|
||||
int sampleRate = inputSampleRate;
|
||||
isInputPcm = isEncodingPcm(inputEncoding);
|
||||
shouldUpResPCMAudio = canConvertHiResPcmToFloat &&
|
||||
(inputEncoding == C.ENCODING_PCM_24BIT || inputEncoding == C.ENCODING_PCM_32BIT);
|
||||
if (isInputPcm) {
|
||||
pcmFrameSize = Util.getPcmFrameSize(inputEncoding, channelCount);
|
||||
}
|
||||
@C.Encoding int encoding = inputEncoding;
|
||||
boolean processingEnabled = isInputPcm && inputEncoding != C.ENCODING_PCM_FLOAT;
|
||||
canApplyPlaybackParams = processingEnabled && !shouldUpResPCMAudio;
|
||||
if (processingEnabled) {
|
||||
AudioProcessor[] availableAudioProcessors = shouldUpResPCMAudio ?
|
||||
toFloatPcmAvailableAudioProcessors : toIntPcmAvailableAudioProcessors;
|
||||
trimmingAudioProcessor.setTrimSampleCount(trimStartSamples, trimEndSamples);
|
||||
channelMappingAudioProcessor.setChannelMap(outputChannels);
|
||||
for (AudioProcessor audioProcessor : availableAudioProcessors) {
|
||||
|
|
@ -460,6 +489,8 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
|
||||
private void resetAudioProcessors() {
|
||||
ArrayList<AudioProcessor> newAudioProcessors = new ArrayList<>();
|
||||
AudioProcessor[] availableAudioProcessors = shouldUpResPCMAudio ?
|
||||
toFloatPcmAvailableAudioProcessors : toIntPcmAvailableAudioProcessors;
|
||||
for (AudioProcessor audioProcessor : availableAudioProcessors) {
|
||||
if (audioProcessor.isActive()) {
|
||||
newAudioProcessors.add(audioProcessor);
|
||||
|
|
@ -808,7 +839,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
|
||||
@Override
|
||||
public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||
if (isInitialized() && !processingEnabled) {
|
||||
if (isInitialized() && !canApplyPlaybackParams) {
|
||||
// The playback parameters are always the default if processing is disabled.
|
||||
this.playbackParameters = PlaybackParameters.DEFAULT;
|
||||
return this.playbackParameters;
|
||||
|
|
@ -964,7 +995,10 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
public void release() {
|
||||
reset();
|
||||
releaseKeepSessionIdAudioTrack();
|
||||
for (AudioProcessor audioProcessor : availableAudioProcessors) {
|
||||
for (AudioProcessor audioProcessor : toIntPcmAvailableAudioProcessors) {
|
||||
audioProcessor.reset();
|
||||
}
|
||||
for (AudioProcessor audioProcessor : toFloatPcmAvailableAudioProcessors) {
|
||||
audioProcessor.reset();
|
||||
}
|
||||
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
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 && encoding != C.ENCODING_PCM_32BIT) {
|
||||
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 || sourceEncoding == C.ENCODING_PCM_32BIT;
|
||||
}
|
||||
|
||||
@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:
|
||||
resampledSize = size;
|
||||
break;
|
||||
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:
|
||||
// 32->32 bit conversion.
|
||||
for (int i = offset; i < limit; i += 4) {
|
||||
int val = inputBuffer.get(i) & 0x000000ff | (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_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.putInt(bits);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue