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:
andrewlewis 2017-02-27 09:00:48 -08:00 committed by Oliver Woodman
parent 2411d0fc76
commit b3cfeaa17b
5 changed files with 230 additions and 16 deletions

View File

@ -270,6 +270,7 @@ public final class AudioTrack {
public static boolean failOnSpuriousAudioTimestamp = false; public static boolean failOnSpuriousAudioTimestamp = false;
private final AudioCapabilities audioCapabilities; private final AudioCapabilities audioCapabilities;
private final ChannelMappingBufferProcessor channelMappingBufferProcessor;
private final BufferProcessor[] availableBufferProcessors; private final BufferProcessor[] availableBufferProcessors;
private final Listener listener; private final Listener listener;
private final ConditionVariable releasingConditionVariable; private final ConditionVariable releasingConditionVariable;
@ -343,9 +344,11 @@ public final class AudioTrack {
public AudioTrack(AudioCapabilities audioCapabilities, BufferProcessor[] bufferProcessors, public AudioTrack(AudioCapabilities audioCapabilities, BufferProcessor[] bufferProcessors,
Listener listener) { Listener listener) {
this.audioCapabilities = audioCapabilities; this.audioCapabilities = audioCapabilities;
availableBufferProcessors = new BufferProcessor[bufferProcessors.length + 1]; channelMappingBufferProcessor = new ChannelMappingBufferProcessor();
availableBufferProcessors = new BufferProcessor[bufferProcessors.length + 2];
availableBufferProcessors[0] = new ResamplingBufferProcessor(); 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; this.listener = listener;
releasingConditionVariable = new ConditionVariable(true); releasingConditionVariable = new ConditionVariable(true);
if (Util.SDK_INT >= 18) { if (Util.SDK_INT >= 18) {
@ -449,6 +452,30 @@ public final class AudioTrack {
*/ */
public void configure(String mimeType, int channelCount, int sampleRate, public void configure(String mimeType, int channelCount, int sampleRate,
@C.PcmEncoding int pcmEncoding, int specifiedBufferSize) throws ConfigurationException { @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); boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType);
@C.Encoding int encoding = passthrough ? getEncodingForMimeType(mimeType) : pcmEncoding; @C.Encoding int encoding = passthrough ? getEncodingForMimeType(mimeType) : pcmEncoding;
boolean flush = false; boolean flush = false;
@ -456,17 +483,15 @@ public final class AudioTrack {
pcmFrameSize = Util.getPcmFrameSize(pcmEncoding, channelCount); pcmFrameSize = Util.getPcmFrameSize(pcmEncoding, channelCount);
// Reconfigure the buffer processors. // Reconfigure the buffer processors.
channelMappingBufferProcessor.setChannelMap(outputChannels);
ArrayList<BufferProcessor> newBufferProcessors = new ArrayList<>(); ArrayList<BufferProcessor> newBufferProcessors = new ArrayList<>();
for (BufferProcessor bufferProcessor : availableBufferProcessors) { for (BufferProcessor bufferProcessor : availableBufferProcessors) {
boolean wasActive = bufferProcessor.isActive();
try { try {
flush |= bufferProcessor.configure(sampleRate, channelCount, encoding); flush |= bufferProcessor.configure(sampleRate, channelCount, encoding);
} catch (BufferProcessor.UnhandledFormatException e) { } catch (BufferProcessor.UnhandledFormatException e) {
throw new ConfigurationException(e); throw new ConfigurationException(e);
} }
boolean isActive = bufferProcessor.isActive(); if (bufferProcessor.isActive()) {
flush |= isActive != wasActive;
if (isActive) {
newBufferProcessors.add(bufferProcessor); newBufferProcessors.add(bufferProcessor);
channelCount = bufferProcessor.getOutputChannelCount(); channelCount = bufferProcessor.getOutputChannelCount();
encoding = bufferProcessor.getOutputEncoding(); encoding = bufferProcessor.getOutputEncoding();

View File

@ -42,16 +42,18 @@ public interface BufferProcessor {
ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0).order(ByteOrder.nativeOrder()); ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0).order(ByteOrder.nativeOrder());
/** /**
* Configures the processor to process input buffers with the specified format and returns whether * Configures the processor to process input buffers with the specified format. After calling this
* the processor must be flushed. After calling this method, {@link #isActive()} returns whether * method, {@link #isActive()} returns whether the processor needs to handle buffers; if not, the
* the processor needs to handle buffers; if not, the processor will not accept any buffers until * processor will not accept any buffers until it is reconfigured. Returns {@code true} if the
* it is reconfigured. {@link #getOutputChannelCount()} and {@link #getOutputEncoding()} return * processor must be flushed, or if the value returned by {@link #isActive()} has changed as a
* the processor's output format. * 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 sampleRateHz The sample rate of input audio in Hz.
* @param channelCount The number of interleaved channels in input audio. * @param channelCount The number of interleaved channels in input audio.
* @param encoding The encoding of 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. * @throws UnhandledFormatException Thrown if the specified format can't be handled as input.
*/ */
boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding) boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding)

View File

@ -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;
}
}

View File

@ -47,8 +47,10 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
private final AudioTrack audioTrack; private final AudioTrack audioTrack;
private boolean passthroughEnabled; private boolean passthroughEnabled;
private boolean codecNeedsDiscardChannelsWorkaround;
private android.media.MediaFormat passthroughMediaFormat; private android.media.MediaFormat passthroughMediaFormat;
private int pcmEncoding; private int pcmEncoding;
private int channelCount;
private long currentPositionUs; private long currentPositionUs;
private boolean allowPositionDiscontinuity; private boolean allowPositionDiscontinuity;
@ -188,6 +190,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Override @Override
protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format, protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format,
MediaCrypto crypto) { MediaCrypto crypto) {
codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name);
if (passthroughEnabled) { if (passthroughEnabled) {
// Override the MIME type used to configure the codec if we are using a passthrough decoder. // Override the MIME type used to configure the codec if we are using a passthrough decoder.
passthroughMediaFormat = format.getFrameworkMediaFormatV16(); passthroughMediaFormat = format.getFrameworkMediaFormatV16();
@ -219,6 +222,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
// output 16-bit PCM. // output 16-bit PCM.
pcmEncoding = MimeTypes.AUDIO_RAW.equals(newFormat.sampleMimeType) ? newFormat.pcmEncoding pcmEncoding = MimeTypes.AUDIO_RAW.equals(newFormat.sampleMimeType) ? newFormat.pcmEncoding
: C.ENCODING_PCM_16BIT; : C.ENCODING_PCM_16BIT;
channelCount = newFormat.channelCount;
} }
@Override @Override
@ -230,8 +234,18 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat; MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat;
int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); 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 { try {
audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0); audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0, channelMap);
} catch (AudioTrack.ConfigurationException e) { } catch (AudioTrack.ConfigurationException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex()); 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 { private final class AudioTrackListener implements AudioTrack.Listener {
@Override @Override

View File

@ -25,6 +25,7 @@ import java.nio.ByteOrder;
*/ */
/* package */ final class ResamplingBufferProcessor implements BufferProcessor { /* package */ final class ResamplingBufferProcessor implements BufferProcessor {
private int sampleRateHz;
private int channelCount; private int channelCount;
@C.PcmEncoding @C.PcmEncoding
private int encoding; 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}. * Creates a new buffer processor that converts audio data to {@link C#ENCODING_PCM_16BIT}.
*/ */
public ResamplingBufferProcessor() { public ResamplingBufferProcessor() {
sampleRateHz = Format.NO_VALUE;
channelCount = Format.NO_VALUE;
encoding = C.ENCODING_INVALID; encoding = C.ENCODING_INVALID;
buffer = EMPTY_BUFFER; buffer = EMPTY_BUFFER;
outputBuffer = EMPTY_BUFFER; outputBuffer = EMPTY_BUFFER;
@ -48,11 +51,12 @@ import java.nio.ByteOrder;
&& encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) { && encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) {
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
} }
this.channelCount = channelCount; if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount
if (this.encoding == encoding) { && this.encoding == encoding) {
return false; return false;
} }
this.sampleRateHz = sampleRateHz;
this.channelCount = channelCount;
this.encoding = encoding; this.encoding = encoding;
if (encoding == C.ENCODING_PCM_16BIT) { if (encoding == C.ENCODING_PCM_16BIT) {
buffer = EMPTY_BUFFER; buffer = EMPTY_BUFFER;