mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Ramp up volume after AudioTrack flush to avoid pop sound
AudioTrack doesn't automatically ramp up the volume after a flush
(only when resuming with play after a pause), which causes audible
pop sounds in most cases. The issue can be avoided by manually
applying a short 20ms volume ramp, the same duration used by the
platform for the automatic volume ramping where available.
Together with the already submitted 6147050b90
, this fixes the
unwanted pop sounds for most cases in the desired way. It only
leaves two cases that are not handled perfectly:
- If the media file itself contains a volume ramp at the beginning,
we wouldn't need this additional ramping. Given the extremely
short duration, this seems ignorable and we can treat it as a
future feature request to mark the beginning of media in a special
way that can then disable the volume ramping.
- For seamless period transitions where we keep using the same
AudioTrack, we may still get a pop sound at the transition. To
solve this, we'd need a dedicated audio processor to either ramp
the end of media down and the beginning of the next item up, or
apply a very short cross-fade. Either way, we need new signalling
to identify cases where the media originates from the same source
and this effect should not be applied (e.g. when re-concatenating
clipped audio snippets from the same file).
PiperOrigin-RevId: 676860234
This commit is contained in:
parent
e887614246
commit
5e3dcea1bf
@ -45,6 +45,7 @@
|
||||
([#1531](https://github.com/androidx/media/issues/1531)).
|
||||
* DataSource:
|
||||
* Audio:
|
||||
* Fix pop sounds that may occur during seeks.
|
||||
* Video:
|
||||
* Add workaround for a device issue on Galaxy Tab S7 FE that causes 60fps
|
||||
secure H264 streams to be marked as unsupported
|
||||
|
@ -18,6 +18,7 @@ package androidx.media3.exoplayer.audio;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static androidx.media3.common.util.Util.constrainValue;
|
||||
import static androidx.media3.common.util.Util.msToUs;
|
||||
import static androidx.media3.exoplayer.audio.AudioCapabilities.DEFAULT_AUDIO_CAPABILITIES;
|
||||
import static androidx.media3.exoplayer.audio.AudioCapabilities.getCapabilities;
|
||||
import static java.lang.Math.max;
|
||||
@ -1143,7 +1144,7 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
if (!buffer.hasRemaining()) {
|
||||
return;
|
||||
}
|
||||
outputBuffer = buffer;
|
||||
outputBuffer = maybeRampUpVolume(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1848,6 +1849,25 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
}
|
||||
}
|
||||
|
||||
private ByteBuffer maybeRampUpVolume(ByteBuffer buffer) {
|
||||
if (configuration.outputMode != OUTPUT_MODE_PCM) {
|
||||
return buffer;
|
||||
}
|
||||
long rampDurationUs = msToUs(AUDIO_TRACK_VOLUME_RAMP_TIME_MS);
|
||||
int rampFrameCount =
|
||||
(int) Util.durationUsToSampleCount(rampDurationUs, configuration.outputSampleRate);
|
||||
long writtenFrames = getWrittenFrames();
|
||||
if (writtenFrames >= rampFrameCount) {
|
||||
return buffer;
|
||||
}
|
||||
return PcmAudioUtil.rampUpVolume(
|
||||
buffer,
|
||||
configuration.outputEncoding,
|
||||
configuration.outputPcmFrameSize,
|
||||
(int) writtenFrames,
|
||||
rampFrameCount);
|
||||
}
|
||||
|
||||
private static void releaseAudioTrackAsync(
|
||||
AudioTrack audioTrack, @Nullable Listener listener, AudioTrackConfig audioTrackConfig) {
|
||||
// AudioTrack.release can take some time, so we call it on a background thread. The background
|
||||
|
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright 2024 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 androidx.media3.exoplayer.audio;
|
||||
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/** Utility methods for PCM audio data. */
|
||||
@UnstableApi
|
||||
public final class PcmAudioUtil {
|
||||
|
||||
/**
|
||||
* Returns a new {@link ByteBuffer} with linear volume ramping applied.
|
||||
*
|
||||
* @param buffer The input buffer containing PCM frames. The buffer will be fully consumed by this
|
||||
* method.
|
||||
* @param pcmEncoding The {@link C.Encoding} of the PCM frames.
|
||||
* @param pcmFrameSize The overall frame size of one PCM frame (including all channels).
|
||||
* @param startFrameIndex The index of the first frame within the audio ramp duration (as
|
||||
* specified by {@code rampFrameCount}).
|
||||
* @param rampFrameCount The overall ramp duration in number of frames.
|
||||
* @return The {@link ByteBuffer} containing the modified PCM data.
|
||||
*/
|
||||
public static ByteBuffer rampUpVolume(
|
||||
ByteBuffer buffer,
|
||||
@C.Encoding int pcmEncoding,
|
||||
int pcmFrameSize,
|
||||
int startFrameIndex,
|
||||
int rampFrameCount) {
|
||||
ByteBuffer outputBuffer =
|
||||
ByteBuffer.allocateDirect(buffer.remaining()).order(ByteOrder.nativeOrder());
|
||||
int frameIndex = startFrameIndex;
|
||||
int frameStartPosition = buffer.position();
|
||||
while (buffer.hasRemaining() && frameIndex < rampFrameCount) {
|
||||
long pcm32Bit = readAs32BitIntPcm(buffer, pcmEncoding);
|
||||
pcm32Bit = pcm32Bit * frameIndex / rampFrameCount;
|
||||
write32BitIntPcm(outputBuffer, (int) pcm32Bit, pcmEncoding);
|
||||
if (buffer.position() == frameStartPosition + pcmFrameSize) {
|
||||
frameIndex++;
|
||||
frameStartPosition = buffer.position();
|
||||
}
|
||||
}
|
||||
outputBuffer.put(buffer);
|
||||
outputBuffer.flip();
|
||||
return outputBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single-channel PCM value from the buffer and returns it as a 32-bit integer PCM value.
|
||||
*
|
||||
* @param buffer The {@link ByteBuffer} to read from.
|
||||
* @param pcmEncoding The {@link C.Encoding} of the PCM data in the buffer.
|
||||
* @return The 32-bit PCM value of the read buffer.
|
||||
*/
|
||||
public static int readAs32BitIntPcm(ByteBuffer buffer, @C.Encoding int pcmEncoding) {
|
||||
switch (pcmEncoding) {
|
||||
case C.ENCODING_PCM_8BIT:
|
||||
return (buffer.get() & 0xFF) << 24;
|
||||
case C.ENCODING_PCM_16BIT:
|
||||
return ((buffer.get() & 0xFF) << 16) | ((buffer.get() & 0xFF) << 24);
|
||||
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
|
||||
return ((buffer.get() & 0xFF) << 24) | ((buffer.get() & 0xFF) << 16);
|
||||
case C.ENCODING_PCM_24BIT:
|
||||
return ((buffer.get() & 0xFF) << 8)
|
||||
| ((buffer.get() & 0xFF) << 16)
|
||||
| ((buffer.get() & 0xFF) << 24);
|
||||
case C.ENCODING_PCM_24BIT_BIG_ENDIAN:
|
||||
return ((buffer.get() & 0xFF) << 24)
|
||||
| ((buffer.get() & 0xFF) << 16)
|
||||
| ((buffer.get() & 0xFF) << 8);
|
||||
case C.ENCODING_PCM_32BIT:
|
||||
return (buffer.get() & 0xFF)
|
||||
| ((buffer.get() & 0xFF) << 8)
|
||||
| ((buffer.get() & 0xFF) << 16)
|
||||
| ((buffer.get() & 0xFF) << 24);
|
||||
case C.ENCODING_PCM_32BIT_BIG_ENDIAN:
|
||||
return ((buffer.get() & 0xFF) << 24)
|
||||
| ((buffer.get() & 0xFF) << 16)
|
||||
| ((buffer.get() & 0xFF) << 8)
|
||||
| (buffer.get() & 0xFF);
|
||||
case C.ENCODING_PCM_FLOAT:
|
||||
float floatValue = Util.constrainValue(buffer.getFloat(), /* min= */ -1f, /* max= */ 1f);
|
||||
if (floatValue < 0) {
|
||||
return (int) (-floatValue * Integer.MIN_VALUE);
|
||||
} else {
|
||||
return (int) (floatValue * Integer.MAX_VALUE);
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a 32-bit integer PCM value to a buffer in the given target PCM encoding.
|
||||
*
|
||||
* @param buffer The {@link ByteBuffer} to write to.
|
||||
* @param pcm32bit The 32-bit PCM value.
|
||||
* @param pcmEncoding The target {@link C.Encoding} of the PCM data in the buffer.
|
||||
*/
|
||||
public static void write32BitIntPcm(
|
||||
ByteBuffer buffer, int pcm32bit, @C.Encoding int pcmEncoding) {
|
||||
switch (pcmEncoding) {
|
||||
case C.ENCODING_PCM_32BIT:
|
||||
buffer.put((byte) pcm32bit);
|
||||
buffer.put((byte) (pcm32bit >> 8));
|
||||
buffer.put((byte) (pcm32bit >> 16));
|
||||
buffer.put((byte) (pcm32bit >> 24));
|
||||
return;
|
||||
case C.ENCODING_PCM_24BIT:
|
||||
buffer.put((byte) (pcm32bit >> 8));
|
||||
buffer.put((byte) (pcm32bit >> 16));
|
||||
buffer.put((byte) (pcm32bit >> 24));
|
||||
return;
|
||||
case C.ENCODING_PCM_16BIT:
|
||||
buffer.put((byte) (pcm32bit >> 16));
|
||||
buffer.put((byte) (pcm32bit >> 24));
|
||||
return;
|
||||
case C.ENCODING_PCM_8BIT:
|
||||
buffer.put((byte) (pcm32bit >> 24));
|
||||
return;
|
||||
case C.ENCODING_PCM_32BIT_BIG_ENDIAN:
|
||||
buffer.put((byte) (pcm32bit >> 24));
|
||||
buffer.put((byte) (pcm32bit >> 16));
|
||||
buffer.put((byte) (pcm32bit >> 8));
|
||||
buffer.put((byte) pcm32bit);
|
||||
return;
|
||||
case C.ENCODING_PCM_24BIT_BIG_ENDIAN:
|
||||
buffer.put((byte) (pcm32bit >> 24));
|
||||
buffer.put((byte) (pcm32bit >> 16));
|
||||
buffer.put((byte) (pcm32bit >> 8));
|
||||
return;
|
||||
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
|
||||
buffer.put((byte) (pcm32bit >> 24));
|
||||
buffer.put((byte) (pcm32bit >> 16));
|
||||
return;
|
||||
case C.ENCODING_PCM_FLOAT:
|
||||
if (pcm32bit < 0) {
|
||||
buffer.putFloat(-((float) pcm32bit) / Integer.MIN_VALUE);
|
||||
} else {
|
||||
buffer.putFloat((float) pcm32bit / Integer.MAX_VALUE);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private PcmAudioUtil() {}
|
||||
}
|
@ -0,0 +1,500 @@
|
||||
/*
|
||||
* Copyright 2024 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 androidx.media3.exoplayer.audio;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.media3.common.C;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Unit tests for {@link PcmAudioUtil}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class PcmAudioUtilTest {
|
||||
|
||||
@Test
|
||||
public void readAs32BitIntPcm_read8Bit_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(5);
|
||||
buffer.put(hexToBytes("80" + "AB" + "00" + "12" + "7F"));
|
||||
buffer.flip();
|
||||
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_8BIT))
|
||||
.isEqualTo(Integer.MIN_VALUE);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_8BIT)).isEqualTo(0xAB000000);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_8BIT)).isEqualTo(0);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_8BIT)).isEqualTo(0x12000000);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_8BIT))
|
||||
.isWithin(0xFFFFFF)
|
||||
.of(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readAs32BitIntPcm_read16Bit_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(10);
|
||||
buffer.put(hexToBytes("0080" + "CDAB" + "0000" + "3412" + "FF7F"));
|
||||
buffer.flip();
|
||||
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_16BIT))
|
||||
.isEqualTo(Integer.MIN_VALUE);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_16BIT)).isEqualTo(0xABCD0000);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_16BIT)).isEqualTo(0);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_16BIT)).isEqualTo(0x12340000);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_16BIT))
|
||||
.isWithin(0xFFFF)
|
||||
.of(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readAs32BitIntPcm_read16BitBigEndian_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(10);
|
||||
buffer.put(hexToBytes("8000" + "ABCD" + "0000" + "1234" + "7FFF"));
|
||||
buffer.flip();
|
||||
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_16BIT_BIG_ENDIAN))
|
||||
.isEqualTo(Integer.MIN_VALUE);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_16BIT_BIG_ENDIAN))
|
||||
.isEqualTo(0xABCD0000);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_16BIT_BIG_ENDIAN))
|
||||
.isEqualTo(0);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_16BIT_BIG_ENDIAN))
|
||||
.isEqualTo(0x12340000);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_16BIT_BIG_ENDIAN))
|
||||
.isWithin(0xFFFF)
|
||||
.of(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readAs32BitIntPcm_read24Bit_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(15);
|
||||
buffer.put(hexToBytes("000080" + "EFCDAB" + "000000" + "563412" + "FFFF7F"));
|
||||
buffer.flip();
|
||||
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_24BIT))
|
||||
.isEqualTo(Integer.MIN_VALUE);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_24BIT)).isEqualTo(0xABCDEF00);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_24BIT)).isEqualTo(0);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_24BIT)).isEqualTo(0x12345600);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_24BIT))
|
||||
.isWithin(0xFF)
|
||||
.of(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readAs32BitIntPcm_read24BitBigEndian_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(15);
|
||||
buffer.put(hexToBytes("800000" + "ABCDEF" + "000000" + "123456" + "7FFFFF"));
|
||||
buffer.flip();
|
||||
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_24BIT_BIG_ENDIAN))
|
||||
.isEqualTo(Integer.MIN_VALUE);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_24BIT_BIG_ENDIAN))
|
||||
.isEqualTo(0xABCDEF00);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_24BIT_BIG_ENDIAN))
|
||||
.isEqualTo(0);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_24BIT_BIG_ENDIAN))
|
||||
.isEqualTo(0x12345600);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_24BIT_BIG_ENDIAN))
|
||||
.isWithin(0xFF)
|
||||
.of(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readAs32BitIntPcm_read32Bit_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(20);
|
||||
buffer.put(hexToBytes("00000080" + "12EFCDAB" + "00000000" + "78563412" + "FFFFFF7F"));
|
||||
buffer.flip();
|
||||
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_32BIT))
|
||||
.isEqualTo(Integer.MIN_VALUE);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_32BIT)).isEqualTo(0xABCDEF12);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_32BIT)).isEqualTo(0);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_32BIT)).isEqualTo(0x12345678);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_32BIT))
|
||||
.isEqualTo(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readAs32BitIntPcm_read32BitBigEndian_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(20);
|
||||
buffer.put(hexToBytes("80000000" + "ABCDEF12" + "00000000" + "12345678" + "7FFFFFFF"));
|
||||
buffer.flip();
|
||||
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_32BIT_BIG_ENDIAN))
|
||||
.isEqualTo(Integer.MIN_VALUE);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_32BIT_BIG_ENDIAN))
|
||||
.isEqualTo(0xABCDEF12);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_32BIT_BIG_ENDIAN))
|
||||
.isEqualTo(0);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_32BIT_BIG_ENDIAN))
|
||||
.isEqualTo(0x12345678);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_32BIT_BIG_ENDIAN))
|
||||
.isEqualTo(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readAs32BitIntPcm_readFloat_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(40);
|
||||
buffer.putFloat(Float.NEGATIVE_INFINITY);
|
||||
buffer.putFloat(-2f);
|
||||
buffer.putFloat(-1f);
|
||||
buffer.putFloat(-0.5f);
|
||||
buffer.putFloat(0f);
|
||||
buffer.putFloat(0.5f);
|
||||
buffer.putFloat(1f);
|
||||
buffer.putFloat(2f);
|
||||
buffer.putFloat(Float.POSITIVE_INFINITY);
|
||||
buffer.putFloat(Float.NaN);
|
||||
buffer.flip();
|
||||
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_FLOAT))
|
||||
.isEqualTo(Integer.MIN_VALUE);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_FLOAT))
|
||||
.isEqualTo(Integer.MIN_VALUE);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_FLOAT))
|
||||
.isEqualTo(Integer.MIN_VALUE);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_FLOAT))
|
||||
.isWithin(1)
|
||||
.of(Integer.MIN_VALUE / 2);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_FLOAT)).isEqualTo(0);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_FLOAT))
|
||||
.isWithin(1)
|
||||
.of(Integer.MAX_VALUE / 2);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_FLOAT))
|
||||
.isEqualTo(Integer.MAX_VALUE);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_FLOAT))
|
||||
.isEqualTo(Integer.MAX_VALUE);
|
||||
// Just make sure we don't crash for NaN.
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(buffer, C.ENCODING_PCM_FLOAT))
|
||||
.isAnyOf(0, Integer.MAX_VALUE, Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void write32BitIntPcm_write8Bit_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(5);
|
||||
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MIN_VALUE, C.ENCODING_PCM_8BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0xABCDEF12, C.ENCODING_PCM_8BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0, C.ENCODING_PCM_8BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0x12345678, C.ENCODING_PCM_8BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MAX_VALUE, C.ENCODING_PCM_8BIT);
|
||||
buffer.flip();
|
||||
|
||||
assertThat(byteBufferToHex(buffer)).isEqualTo("80" + "AB" + "00" + "12" + "7F");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void write32BitIntPcm_write16Bit_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(10);
|
||||
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MIN_VALUE, C.ENCODING_PCM_16BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0xABCDEF12, C.ENCODING_PCM_16BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0, C.ENCODING_PCM_16BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0x12345678, C.ENCODING_PCM_16BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MAX_VALUE, C.ENCODING_PCM_16BIT);
|
||||
buffer.flip();
|
||||
|
||||
assertThat(byteBufferToHex(buffer)).isEqualTo("0080" + "CDAB" + "0000" + "3412" + "FF7F");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void write32BitIntPcm_write16BitBigEndian_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(10);
|
||||
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MIN_VALUE, C.ENCODING_PCM_16BIT_BIG_ENDIAN);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0xABCDEF12, C.ENCODING_PCM_16BIT_BIG_ENDIAN);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0, C.ENCODING_PCM_16BIT_BIG_ENDIAN);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0x12345678, C.ENCODING_PCM_16BIT_BIG_ENDIAN);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MAX_VALUE, C.ENCODING_PCM_16BIT_BIG_ENDIAN);
|
||||
buffer.flip();
|
||||
|
||||
assertThat(byteBufferToHex(buffer)).isEqualTo("8000" + "ABCD" + "0000" + "1234" + "7FFF");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void write32BitIntPcm_write24Bit_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(15);
|
||||
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MIN_VALUE, C.ENCODING_PCM_24BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0xABCDEF12, C.ENCODING_PCM_24BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0, C.ENCODING_PCM_24BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0x12345678, C.ENCODING_PCM_24BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MAX_VALUE, C.ENCODING_PCM_24BIT);
|
||||
buffer.flip();
|
||||
|
||||
assertThat(byteBufferToHex(buffer))
|
||||
.isEqualTo("000080" + "EFCDAB" + "000000" + "563412" + "FFFF7F");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void write32BitIntPcm_write24BitBigEndian_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(15);
|
||||
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MIN_VALUE, C.ENCODING_PCM_24BIT_BIG_ENDIAN);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0xABCDEF12, C.ENCODING_PCM_24BIT_BIG_ENDIAN);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0, C.ENCODING_PCM_24BIT_BIG_ENDIAN);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0x12345678, C.ENCODING_PCM_24BIT_BIG_ENDIAN);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MAX_VALUE, C.ENCODING_PCM_24BIT_BIG_ENDIAN);
|
||||
buffer.flip();
|
||||
|
||||
assertThat(byteBufferToHex(buffer))
|
||||
.isEqualTo("800000" + "ABCDEF" + "000000" + "123456" + "7FFFFF");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void write32BitIntPcm_write32Bit_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(20);
|
||||
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MIN_VALUE, C.ENCODING_PCM_32BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0xABCDEF12, C.ENCODING_PCM_32BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0, C.ENCODING_PCM_32BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0x12345678, C.ENCODING_PCM_32BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MAX_VALUE, C.ENCODING_PCM_32BIT);
|
||||
buffer.flip();
|
||||
|
||||
assertThat(byteBufferToHex(buffer))
|
||||
.isEqualTo("00000080" + "12EFCDAB" + "00000000" + "78563412" + "FFFFFF7F");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void write32BitIntPcm_write32BitBigEndian_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(20);
|
||||
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MIN_VALUE, C.ENCODING_PCM_32BIT_BIG_ENDIAN);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0xABCDEF12, C.ENCODING_PCM_32BIT_BIG_ENDIAN);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0, C.ENCODING_PCM_32BIT_BIG_ENDIAN);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0x12345678, C.ENCODING_PCM_32BIT_BIG_ENDIAN);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MAX_VALUE, C.ENCODING_PCM_32BIT_BIG_ENDIAN);
|
||||
buffer.flip();
|
||||
|
||||
assertThat(byteBufferToHex(buffer))
|
||||
.isEqualTo("80000000" + "ABCDEF12" + "00000000" + "12345678" + "7FFFFFFF");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void write32BitIntPcm_writeFloat_returnsExpectedValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(20);
|
||||
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MIN_VALUE, C.ENCODING_PCM_FLOAT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MIN_VALUE / 2, C.ENCODING_PCM_FLOAT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, 0, C.ENCODING_PCM_FLOAT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MAX_VALUE / 2, C.ENCODING_PCM_FLOAT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, Integer.MAX_VALUE, C.ENCODING_PCM_FLOAT);
|
||||
buffer.flip();
|
||||
|
||||
assertThat(buffer.getFloat()).isEqualTo(-1f);
|
||||
assertThat(buffer.getFloat()).isEqualTo(-0.5f);
|
||||
assertThat(buffer.getFloat()).isEqualTo(0f);
|
||||
assertThat(buffer.getFloat()).isEqualTo(0.5f);
|
||||
assertThat(buffer.getFloat()).isEqualTo(1f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rampUpVolume_fromZeroFullRamp_returnsCorrectlyScaledValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(48);
|
||||
// Intentionally use large values to check for overflows.
|
||||
int a = Integer.MAX_VALUE;
|
||||
int b = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, a, C.ENCODING_PCM_32BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, b, C.ENCODING_PCM_32BIT);
|
||||
}
|
||||
buffer.flip();
|
||||
|
||||
ByteBuffer output =
|
||||
PcmAudioUtil.rampUpVolume(
|
||||
buffer,
|
||||
C.ENCODING_PCM_32BIT,
|
||||
/* pcmFrameSize= */ 8,
|
||||
/* startFrameIndex= */ 0,
|
||||
/* rampFrameCount= */ 4);
|
||||
|
||||
ImmutableList.Builder<Integer> outputValues = ImmutableList.builder();
|
||||
while (output.hasRemaining()) {
|
||||
outputValues.add(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_32BIT));
|
||||
}
|
||||
int threeQuartersA = (int) ((long) a * 3 / 4);
|
||||
int threeQuartersB = (int) ((long) b * 3 / 4);
|
||||
assertThat(outputValues.build())
|
||||
.containsExactly(
|
||||
0, 0, a / 4, b / 4, a / 2, b / 2, threeQuartersA, threeQuartersB, a, b, a, b)
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rampUpVolume_fromNonZeroFullRamp_returnsCorrectlyScaledValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(32);
|
||||
// Intentionally use large values to check for overflows.
|
||||
int a = Integer.MAX_VALUE;
|
||||
int b = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, a, C.ENCODING_PCM_32BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, b, C.ENCODING_PCM_32BIT);
|
||||
}
|
||||
buffer.flip();
|
||||
|
||||
ByteBuffer output =
|
||||
PcmAudioUtil.rampUpVolume(
|
||||
buffer,
|
||||
C.ENCODING_PCM_32BIT,
|
||||
/* pcmFrameSize= */ 8,
|
||||
/* startFrameIndex= */ 2,
|
||||
/* rampFrameCount= */ 4);
|
||||
|
||||
ImmutableList.Builder<Integer> outputValues = ImmutableList.builder();
|
||||
while (output.hasRemaining()) {
|
||||
outputValues.add(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_32BIT));
|
||||
}
|
||||
int threeQuartersA = (int) ((long) a * 3 / 4);
|
||||
int threeQuartersB = (int) ((long) b * 3 / 4);
|
||||
assertThat(outputValues.build())
|
||||
.containsExactly(a / 2, b / 2, threeQuartersA, threeQuartersB, a, b, a, b)
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rampUpVolume_fromZeroPartialRamp_returnsCorrectlyScaledValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(24);
|
||||
// Intentionally use large values to check for overflows.
|
||||
int a = Integer.MAX_VALUE;
|
||||
int b = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, a, C.ENCODING_PCM_32BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, b, C.ENCODING_PCM_32BIT);
|
||||
}
|
||||
buffer.flip();
|
||||
|
||||
ByteBuffer output =
|
||||
PcmAudioUtil.rampUpVolume(
|
||||
buffer,
|
||||
C.ENCODING_PCM_32BIT,
|
||||
/* pcmFrameSize= */ 8,
|
||||
/* startFrameIndex= */ 0,
|
||||
/* rampFrameCount= */ 4);
|
||||
|
||||
ImmutableList.Builder<Integer> outputValues = ImmutableList.builder();
|
||||
while (output.hasRemaining()) {
|
||||
outputValues.add(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_32BIT));
|
||||
}
|
||||
assertThat(outputValues.build()).containsExactly(0, 0, a / 4, b / 4, a / 2, b / 2).inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rampUpVolume_fromNonZeroPartialRamp_returnsCorrectlyScaledValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(48);
|
||||
// Intentionally use large values to check for overflows.
|
||||
int a = Integer.MAX_VALUE;
|
||||
int b = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, a, C.ENCODING_PCM_32BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, b, C.ENCODING_PCM_32BIT);
|
||||
}
|
||||
buffer.flip();
|
||||
|
||||
ByteBuffer output =
|
||||
PcmAudioUtil.rampUpVolume(
|
||||
buffer,
|
||||
C.ENCODING_PCM_32BIT,
|
||||
/* pcmFrameSize= */ 8,
|
||||
/* startFrameIndex= */ 1,
|
||||
/* rampFrameCount= */ 4);
|
||||
|
||||
ImmutableList.Builder<Integer> outputValues = ImmutableList.builder();
|
||||
while (output.hasRemaining()) {
|
||||
outputValues.add(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_32BIT));
|
||||
}
|
||||
assertThat(outputValues.build()).containsExactly(a / 4, b / 4, a / 2, b / 2).inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rampUpVolume_non32Bit_returnsCorrectlyScaledValues() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(48);
|
||||
// Intentionally use large values to check for overflows.
|
||||
int a = Integer.MAX_VALUE;
|
||||
int b = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, a, C.ENCODING_PCM_16BIT);
|
||||
PcmAudioUtil.write32BitIntPcm(buffer, b, C.ENCODING_PCM_16BIT);
|
||||
}
|
||||
buffer.flip();
|
||||
|
||||
ByteBuffer output =
|
||||
PcmAudioUtil.rampUpVolume(
|
||||
buffer,
|
||||
C.ENCODING_PCM_16BIT,
|
||||
/* pcmFrameSize= */ 4,
|
||||
/* startFrameIndex= */ 0,
|
||||
/* rampFrameCount= */ 4);
|
||||
|
||||
int threeQuartersA = (int) ((long) a * 3 / 4);
|
||||
int threeQuartersB = (int) ((long) b * 3 / 4);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_16BIT)).isWithin(0xFFFF).of(0);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_16BIT)).isWithin(0xFFFF).of(0);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_16BIT))
|
||||
.isWithin(0xFFFF)
|
||||
.of(a / 4);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_16BIT))
|
||||
.isWithin(0xFFFF)
|
||||
.of(b / 4);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_16BIT))
|
||||
.isWithin(0xFFFF)
|
||||
.of(a / 2);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_16BIT))
|
||||
.isWithin(0xFFFF)
|
||||
.of(b / 2);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_16BIT))
|
||||
.isWithin(0xFFFF)
|
||||
.of(threeQuartersA);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_16BIT))
|
||||
.isWithin(0xFFFF)
|
||||
.of(threeQuartersB);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_16BIT)).isWithin(0xFFFF).of(a);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_16BIT)).isWithin(0xFFFF).of(b);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_16BIT)).isWithin(0xFFFF).of(a);
|
||||
assertThat(PcmAudioUtil.readAs32BitIntPcm(output, C.ENCODING_PCM_16BIT)).isWithin(0xFFFF).of(b);
|
||||
}
|
||||
|
||||
private byte[] hexToBytes(String hexString) {
|
||||
byte[] bytes = new BigInteger(hexString, 16).toByteArray();
|
||||
// Remove or add leading zeros to match the expected length.
|
||||
int expectedLength = hexString.length() / 2;
|
||||
if (bytes.length > expectedLength) {
|
||||
bytes = Arrays.copyOfRange(bytes, 1, bytes.length);
|
||||
} else if (bytes.length < expectedLength) {
|
||||
byte[] newBytes = new byte[expectedLength];
|
||||
System.arraycopy(
|
||||
bytes,
|
||||
/* srcPos= */ 0,
|
||||
newBytes,
|
||||
/* destPos= */ expectedLength - bytes.length,
|
||||
bytes.length);
|
||||
bytes = newBytes;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static String byteBufferToHex(ByteBuffer buffer) {
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
while (buffer.hasRemaining()) {
|
||||
hexString.append(String.format("%02X", buffer.get()));
|
||||
}
|
||||
return hexString.toString();
|
||||
}
|
||||
}
|
@ -119,8 +119,12 @@ public class EndToEndGaplessTest {
|
||||
// Track two is only trimmed at its beginning, but not its end.
|
||||
Arrays.copyOfRange(
|
||||
decoderOutputBytes, bytesPerAudioFile + delayBytes, decoderOutputBytes.length));
|
||||
|
||||
byte[] audioTrackReceivedBytes = audioTrackListener.getAllReceivedBytes();
|
||||
|
||||
// The first few bytes can be modified to ramp up the volume. Exclude those from the comparison.
|
||||
Arrays.fill(expectedTrimmedByteContent, 0, 2000, (byte) 0);
|
||||
Arrays.fill(audioTrackReceivedBytes, 0, 2000, (byte) 0);
|
||||
|
||||
assertThat(audioTrackReceivedBytes).isEqualTo(expectedTrimmedByteContent);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user