Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support in mediacodecaudiorenderer for 24bit pcm to float #3635

Merged
merged 3 commits into from
Jan 24, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,12 @@ public InvalidAudioTrackTimestampException(String message) {
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[] hiResAvailableAudioProcessors;
private final ConditionVariable releasingConditionVariable;
private final long[] playheadOffsets;
private final AudioTrackUtil audioTrackUtil;
Expand All @@ -180,6 +182,7 @@ public InvalidAudioTrackTimestampException(String message) {
private AudioTrack keepSessionIdAudioTrack;
private AudioTrack audioTrack;
private boolean isInputPcm;
private boolean shouldUpResPCMAudio;
private int inputSampleRate;
private int sampleRate;
private int channelConfig;
Expand Down Expand Up @@ -233,6 +236,8 @@ public InvalidAudioTrackTimestampException(String message) {
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.
Expand All @@ -241,7 +246,23 @@ public InvalidAudioTrackTimestampException(String message) {
*/
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 {
Expand All @@ -265,6 +286,8 @@ public DefaultAudioSink(@Nullable AudioCapabilities audioCapabilities,
availableAudioProcessors[2] = trimmingAudioProcessor;
System.arraycopy(audioProcessors, 0, availableAudioProcessors, 3, audioProcessors.length);
availableAudioProcessors[3 + audioProcessors.length] = sonicAudioProcessor;
hiResAvailableAudioProcessors = new AudioProcessor[1];
hiResAvailableAudioProcessors[0] = new FloatResamplingAudioProcessor();
playheadOffsets = new long[MAX_PLAYHEAD_OFFSET_COUNT];
volume = 1.0f;
startMediaTimeState = START_NOT_SET;
Expand Down Expand Up @@ -342,15 +365,20 @@ public void configure(@C.Encoding int inputEncoding, int inputChannelCount, int
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);
pcmFrameSize = Util.getPcmFrameSize(shouldUpResPCMAudio
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pcmFrameSize is used to calculate the number of submitted frames (not the number of output frames), so shouldn't this be left as it was?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think that was a mixing of changes that got left in and caused discontinuity errors. Fixed.

? C.ENCODING_PCM_FLOAT : inputEncoding, channelCount);
}
@C.Encoding int encoding = inputEncoding;
boolean processingEnabled = isInputPcm && inputEncoding != C.ENCODING_PCM_FLOAT;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the 24-bit integer PCM -> float path we can't apply playback parameters, but processingEnabled gets set to true so setPlaybackParameters will try to apply the playback parameters. For now shall we just add another flag canApplyPlaybackParams which is processingEnabled && !shouldUpResPCMAudio and change the check in setPlaybackParameters to use that?

if (processingEnabled) {
AudioProcessor[] activeAudioProcessors = shouldUpResPCMAudio ?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "active" part of the name may be misleading because these audio processors won't necessarily all end up being active (in the terminology of AudioProcessor.isActive()).

How about renaming the final fields to something like toIntPcmAvailableAudioProcessors and toFloatPcmAvailableAudioProcessors? The names are a bit long but hopefully clearer. Or, if not doing that rename, this line could be AudioProcessor[] availableAudioProcessors = shouldUpResPCMAudio ? hiResAvailableAudioProcessors : this.availableAudioProcessors. Same applies for other occurrence of activeAudioProcessors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did the rename and changed the local activeAudioProcessors line to availableAudioProcessors

hiResAvailableAudioProcessors : availableAudioProcessors;
trimmingAudioProcessor.setTrimSampleCount(trimStartSamples, trimEndSamples);
channelMappingAudioProcessor.setChannelMap(outputChannels);
for (AudioProcessor audioProcessor : availableAudioProcessors) {
for (AudioProcessor audioProcessor : activeAudioProcessors) {
try {
flush |= audioProcessor.configure(sampleRate, channelCount, encoding);
} catch (AudioProcessor.UnhandledFormatException e) {
Expand Down Expand Up @@ -460,7 +488,9 @@ public void configure(@C.Encoding int inputEncoding, int inputChannelCount, int

private void resetAudioProcessors() {
ArrayList<AudioProcessor> newAudioProcessors = new ArrayList<>();
for (AudioProcessor audioProcessor : availableAudioProcessors) {
AudioProcessor[] activeAudioProcessors = shouldUpResPCMAudio ?
hiResAvailableAudioProcessors : availableAudioProcessors;
for (AudioProcessor audioProcessor : activeAudioProcessors) {
if (audioProcessor.isActive()) {
newAudioProcessors.add(audioProcessor);
} else {
Expand Down Expand Up @@ -967,6 +997,9 @@ public void release() {
for (AudioProcessor audioProcessor : availableAudioProcessors) {
audioProcessor.reset();
}
for (AudioProcessor audioProcessor : hiResAvailableAudioProcessors) {
audioProcessor.reset();
}
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
playing = false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package com.google.android.exoplayer2.audio;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please could you add an AOSP header? (Just copy from another file and replace the year with 2018.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check



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.put((byte) (bits & 0xff));
buffer.put((byte) ((bits >> 8) & 0xff));
buffer.put((byte) ((bits >> 16) & 0xff));
buffer.put((byte) ((bits >> 24) & 0xff));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does buffer.putLong(bits) work here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

putInt works

}

}