Discard extra silent channels on Samsung Galaxy S6/S7.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=148654495
This commit is contained in:
parent
2411d0fc76
commit
b3cfeaa17b
@ -270,6 +270,7 @@ public final class AudioTrack {
|
||||
public static boolean failOnSpuriousAudioTimestamp = false;
|
||||
|
||||
private final AudioCapabilities audioCapabilities;
|
||||
private final ChannelMappingBufferProcessor channelMappingBufferProcessor;
|
||||
private final BufferProcessor[] availableBufferProcessors;
|
||||
private final Listener listener;
|
||||
private final ConditionVariable releasingConditionVariable;
|
||||
@ -343,9 +344,11 @@ public final class AudioTrack {
|
||||
public AudioTrack(AudioCapabilities audioCapabilities, BufferProcessor[] bufferProcessors,
|
||||
Listener listener) {
|
||||
this.audioCapabilities = audioCapabilities;
|
||||
availableBufferProcessors = new BufferProcessor[bufferProcessors.length + 1];
|
||||
channelMappingBufferProcessor = new ChannelMappingBufferProcessor();
|
||||
availableBufferProcessors = new BufferProcessor[bufferProcessors.length + 2];
|
||||
availableBufferProcessors[0] = new ResamplingBufferProcessor();
|
||||
System.arraycopy(bufferProcessors, 0, availableBufferProcessors, 1, bufferProcessors.length);
|
||||
availableBufferProcessors[1] = channelMappingBufferProcessor;
|
||||
System.arraycopy(bufferProcessors, 0, availableBufferProcessors, 2, bufferProcessors.length);
|
||||
this.listener = listener;
|
||||
releasingConditionVariable = new ConditionVariable(true);
|
||||
if (Util.SDK_INT >= 18) {
|
||||
@ -449,6 +452,30 @@ public final class AudioTrack {
|
||||
*/
|
||||
public void configure(String mimeType, int channelCount, int sampleRate,
|
||||
@C.PcmEncoding int pcmEncoding, int specifiedBufferSize) throws ConfigurationException {
|
||||
configure(mimeType, channelCount, sampleRate, pcmEncoding, specifiedBufferSize, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures (or reconfigures) the audio track.
|
||||
*
|
||||
* @param mimeType The mime type.
|
||||
* @param channelCount The number of channels.
|
||||
* @param sampleRate The sample rate in Hz.
|
||||
* @param pcmEncoding For PCM formats, the encoding used. One of {@link C#ENCODING_PCM_16BIT},
|
||||
* {@link C#ENCODING_PCM_16BIT}, {@link C#ENCODING_PCM_24BIT} and
|
||||
* {@link C#ENCODING_PCM_32BIT}.
|
||||
* @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a
|
||||
* suitable buffer size automatically.
|
||||
* @param outputChannels A mapping from input to output channels that is applied to this track's
|
||||
* input as a preprocessing step, if handling PCM input. Specify {@code null} to leave the
|
||||
* input unchanged. Otherwise, the element at index {@code i} specifies index of the input
|
||||
* channel to map to output channel {@code i} when preprocessing input buffers. After the
|
||||
* map is applied the audio data will have {@code outputChannels.length} channels.
|
||||
* @throws ConfigurationException If an error occurs configuring the track.
|
||||
*/
|
||||
public void configure(String mimeType, int channelCount, int sampleRate,
|
||||
@C.PcmEncoding int pcmEncoding, int specifiedBufferSize, int[] outputChannels)
|
||||
throws ConfigurationException {
|
||||
boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType);
|
||||
@C.Encoding int encoding = passthrough ? getEncodingForMimeType(mimeType) : pcmEncoding;
|
||||
boolean flush = false;
|
||||
@ -456,17 +483,15 @@ public final class AudioTrack {
|
||||
pcmFrameSize = Util.getPcmFrameSize(pcmEncoding, channelCount);
|
||||
|
||||
// Reconfigure the buffer processors.
|
||||
channelMappingBufferProcessor.setChannelMap(outputChannels);
|
||||
ArrayList<BufferProcessor> newBufferProcessors = new ArrayList<>();
|
||||
for (BufferProcessor bufferProcessor : availableBufferProcessors) {
|
||||
boolean wasActive = bufferProcessor.isActive();
|
||||
try {
|
||||
flush |= bufferProcessor.configure(sampleRate, channelCount, encoding);
|
||||
} catch (BufferProcessor.UnhandledFormatException e) {
|
||||
throw new ConfigurationException(e);
|
||||
}
|
||||
boolean isActive = bufferProcessor.isActive();
|
||||
flush |= isActive != wasActive;
|
||||
if (isActive) {
|
||||
if (bufferProcessor.isActive()) {
|
||||
newBufferProcessors.add(bufferProcessor);
|
||||
channelCount = bufferProcessor.getOutputChannelCount();
|
||||
encoding = bufferProcessor.getOutputEncoding();
|
||||
|
@ -42,16 +42,18 @@ public interface BufferProcessor {
|
||||
ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0).order(ByteOrder.nativeOrder());
|
||||
|
||||
/**
|
||||
* Configures the processor to process input buffers with the specified format and returns whether
|
||||
* the processor must be flushed. After calling this method, {@link #isActive()} returns whether
|
||||
* the processor needs to handle buffers; if not, the processor will not accept any buffers until
|
||||
* it is reconfigured. {@link #getOutputChannelCount()} and {@link #getOutputEncoding()} return
|
||||
* the processor's output format.
|
||||
* Configures the processor to process input buffers with the specified format. After calling this
|
||||
* method, {@link #isActive()} returns whether the processor needs to handle buffers; if not, the
|
||||
* processor will not accept any buffers until it is reconfigured. Returns {@code true} if the
|
||||
* processor must be flushed, or if the value returned by {@link #isActive()} has changed as a
|
||||
* result of the call. If it's active, {@link #getOutputChannelCount()} and
|
||||
* {@link #getOutputEncoding()} return the processor's output format.
|
||||
*
|
||||
* @param sampleRateHz The sample rate of input audio in Hz.
|
||||
* @param channelCount The number of interleaved channels in input audio.
|
||||
* @param encoding The encoding of input audio.
|
||||
* @return Whether the processor must be flushed.
|
||||
* @return {@code true} if the processor must be flushed or the value returned by
|
||||
* {@link #isActive()} has changed as a result of the call.
|
||||
* @throws UnhandledFormatException Thrown if the specified format can't be handled as input.
|
||||
*/
|
||||
boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding)
|
||||
|
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.C.Encoding;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Buffer processor that applies a mapping from input channels onto specified output channels. This
|
||||
* can be used to reorder, duplicate or discard channels.
|
||||
*/
|
||||
/* package */ final class ChannelMappingBufferProcessor implements BufferProcessor {
|
||||
|
||||
private int channelCount;
|
||||
private int sampleRateHz;
|
||||
private int[] pendingOutputChannels;
|
||||
|
||||
private boolean active;
|
||||
private int[] outputChannels;
|
||||
private ByteBuffer buffer;
|
||||
private ByteBuffer outputBuffer;
|
||||
private boolean inputEnded;
|
||||
|
||||
/**
|
||||
* Creates a new processor that applies a channel mapping.
|
||||
*/
|
||||
public ChannelMappingBufferProcessor() {
|
||||
buffer = EMPTY_BUFFER;
|
||||
outputBuffer = EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the channel mapping. After calling this method, call {@link #configure(int, int, int)}
|
||||
* to start using the new channel map.
|
||||
*
|
||||
* @see AudioTrack#configure(String, int, int, int, int, int[])
|
||||
*/
|
||||
public void setChannelMap(int[] outputChannels) {
|
||||
pendingOutputChannels = outputChannels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configure(int sampleRateHz, int channelCount, @Encoding int encoding)
|
||||
throws UnhandledFormatException {
|
||||
boolean outputChannelsChanged = !Arrays.equals(pendingOutputChannels, outputChannels);
|
||||
outputChannels = pendingOutputChannels;
|
||||
if (outputChannels == null) {
|
||||
active = false;
|
||||
return outputChannelsChanged;
|
||||
}
|
||||
if (encoding != C.ENCODING_PCM_16BIT) {
|
||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
||||
}
|
||||
if (!outputChannelsChanged && this.sampleRateHz == sampleRateHz
|
||||
&& this.channelCount == channelCount) {
|
||||
return false;
|
||||
}
|
||||
this.sampleRateHz = sampleRateHz;
|
||||
this.channelCount = channelCount;
|
||||
|
||||
active = channelCount != outputChannels.length;
|
||||
for (int i = 0; i < outputChannels.length; i++) {
|
||||
int channelIndex = outputChannels[i];
|
||||
if (channelIndex >= channelCount) {
|
||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
||||
}
|
||||
active |= (channelIndex != i);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOutputChannelCount() {
|
||||
return outputChannels == null ? channelCount : outputChannels.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOutputEncoding() {
|
||||
return C.ENCODING_PCM_16BIT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueInput(ByteBuffer inputBuffer) {
|
||||
int position = inputBuffer.position();
|
||||
int limit = inputBuffer.limit();
|
||||
int frameCount = (limit - position) / (2 * channelCount);
|
||||
int outputSize = frameCount * outputChannels.length * 2;
|
||||
if (buffer.capacity() < outputSize) {
|
||||
buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder());
|
||||
} else {
|
||||
buffer.clear();
|
||||
}
|
||||
while (position < limit) {
|
||||
for (int channelIndex : outputChannels) {
|
||||
buffer.putShort(inputBuffer.getShort(position + 2 * channelIndex));
|
||||
}
|
||||
position += channelCount * 2;
|
||||
}
|
||||
inputBuffer.position(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 release() {
|
||||
flush();
|
||||
buffer = EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
}
|
@ -47,8 +47,10 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||
private final AudioTrack audioTrack;
|
||||
|
||||
private boolean passthroughEnabled;
|
||||
private boolean codecNeedsDiscardChannelsWorkaround;
|
||||
private android.media.MediaFormat passthroughMediaFormat;
|
||||
private int pcmEncoding;
|
||||
private int channelCount;
|
||||
private long currentPositionUs;
|
||||
private boolean allowPositionDiscontinuity;
|
||||
|
||||
@ -188,6 +190,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||
@Override
|
||||
protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format,
|
||||
MediaCrypto crypto) {
|
||||
codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name);
|
||||
if (passthroughEnabled) {
|
||||
// Override the MIME type used to configure the codec if we are using a passthrough decoder.
|
||||
passthroughMediaFormat = format.getFrameworkMediaFormatV16();
|
||||
@ -219,6 +222,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||
// output 16-bit PCM.
|
||||
pcmEncoding = MimeTypes.AUDIO_RAW.equals(newFormat.sampleMimeType) ? newFormat.pcmEncoding
|
||||
: C.ENCODING_PCM_16BIT;
|
||||
channelCount = newFormat.channelCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -230,8 +234,18 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||
MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat;
|
||||
int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
|
||||
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
|
||||
int[] channelMap;
|
||||
if (codecNeedsDiscardChannelsWorkaround && channelCount == 6 && this.channelCount < 6) {
|
||||
channelMap = new int[this.channelCount];
|
||||
for (int i = 0; i < this.channelCount; i++) {
|
||||
channelMap[i] = i;
|
||||
}
|
||||
} else {
|
||||
channelMap = null;
|
||||
}
|
||||
|
||||
try {
|
||||
audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0);
|
||||
audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0, channelMap);
|
||||
} catch (AudioTrack.ConfigurationException e) {
|
||||
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
||||
}
|
||||
@ -388,6 +402,20 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the decoder is known to output six audio channels when provided with input with
|
||||
* fewer than six channels.
|
||||
* <p>
|
||||
* See [Internal: b/35655036].
|
||||
*/
|
||||
private static boolean codecNeedsDiscardChannelsWorkaround(String codecName) {
|
||||
// The workaround applies to Samsung Galaxy S6 and Samsung Galaxy S7.
|
||||
return Util.SDK_INT < 24 && "OMX.SEC.aac.dec".equals(codecName)
|
||||
&& "samsung".equals(Util.MANUFACTURER)
|
||||
&& (Util.DEVICE.startsWith("zeroflte") || Util.DEVICE.startsWith("herolte")
|
||||
|| Util.DEVICE.startsWith("heroqlte"));
|
||||
}
|
||||
|
||||
private final class AudioTrackListener implements AudioTrack.Listener {
|
||||
|
||||
@Override
|
||||
|
@ -25,6 +25,7 @@ import java.nio.ByteOrder;
|
||||
*/
|
||||
/* package */ final class ResamplingBufferProcessor implements BufferProcessor {
|
||||
|
||||
private int sampleRateHz;
|
||||
private int channelCount;
|
||||
@C.PcmEncoding
|
||||
private int encoding;
|
||||
@ -36,6 +37,8 @@ import java.nio.ByteOrder;
|
||||
* Creates a new buffer processor that converts audio data to {@link C#ENCODING_PCM_16BIT}.
|
||||
*/
|
||||
public ResamplingBufferProcessor() {
|
||||
sampleRateHz = Format.NO_VALUE;
|
||||
channelCount = Format.NO_VALUE;
|
||||
encoding = C.ENCODING_INVALID;
|
||||
buffer = EMPTY_BUFFER;
|
||||
outputBuffer = EMPTY_BUFFER;
|
||||
@ -48,11 +51,12 @@ import java.nio.ByteOrder;
|
||||
&& encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) {
|
||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
||||
}
|
||||
this.channelCount = channelCount;
|
||||
if (this.encoding == encoding) {
|
||||
if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount
|
||||
&& this.encoding == encoding) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.sampleRateHz = sampleRateHz;
|
||||
this.channelCount = channelCount;
|
||||
this.encoding = encoding;
|
||||
if (encoding == C.ENCODING_PCM_16BIT) {
|
||||
buffer = EMPTY_BUFFER;
|
||||
|
Loading…
x
Reference in New Issue
Block a user