-
Notifications
You must be signed in to change notification settings - Fork 6k
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
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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; | ||
|
@@ -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. | ||
|
@@ -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 { | ||
|
@@ -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; | ||
|
@@ -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 | ||
? C.ENCODING_PCM_FLOAT : inputEncoding, channelCount); | ||
} | ||
@C.Encoding int encoding = inputEncoding; | ||
boolean processingEnabled = isInputPcm && inputEncoding != C.ENCODING_PCM_FLOAT; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
if (processingEnabled) { | ||
AudioProcessor[] activeAudioProcessors = shouldUpResPCMAudio ? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 How about renaming the final fields to something like There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
@@ -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 { | ||
|
@@ -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; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
package com.google.android.exoplayer2.audio; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. putInt works |
||
} | ||
|
||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.