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;
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();

View File

@ -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)

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 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

View File

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