Implement constant power matrices for channel mixing to mono & stereo.

Calculations are derived from the android platform channel mixing.

PiperOrigin-RevId: 689427658
This commit is contained in:
samrobinson 2024-10-24 10:19:03 -07:00 committed by Copybara-Service
parent d2fb779929
commit 45317394da
2 changed files with 162 additions and 38 deletions

View File

@ -109,7 +109,6 @@ import java.io.File;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -417,20 +416,9 @@ public final class TransformerActivity extends AppCompatActivity {
if (mixToMono || scaleVolumeToHalf) { if (mixToMono || scaleVolumeToHalf) {
ChannelMixingAudioProcessor mixingAudioProcessor = new ChannelMixingAudioProcessor(); ChannelMixingAudioProcessor mixingAudioProcessor = new ChannelMixingAudioProcessor();
for (int inputChannelCount = 1; inputChannelCount <= 6; inputChannelCount++) { for (int inputChannelCount = 1; inputChannelCount <= 6; inputChannelCount++) {
ChannelMixingMatrix matrix; ChannelMixingMatrix matrix =
if (mixToMono) { ChannelMixingMatrix.createForConstantPower(
float[] mixingCoefficients = new float[inputChannelCount]; inputChannelCount, /* outputChannelCount= */ mixToMono ? 1 : inputChannelCount);
// Each channel is equally weighted in the mix to mono.
Arrays.fill(mixingCoefficients, 1f / inputChannelCount);
matrix =
new ChannelMixingMatrix(
inputChannelCount, /* outputChannelCount= */ 1, mixingCoefficients);
} else {
// Identity matrix.
matrix =
ChannelMixingMatrix.create(
inputChannelCount, /* outputChannelCount= */ inputChannelCount);
}
// Apply the volume adjustment. // Apply the volume adjustment.
mixingAudioProcessor.putChannelMixingMatrix( mixingAudioProcessor.putChannelMixingMatrix(

View File

@ -18,6 +18,7 @@ package androidx.media3.common.audio;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
/** /**
* An immutable matrix that describes the mapping of input channels to output channels. * An immutable matrix that describes the mapping of input channels to output channels.
@ -51,8 +52,8 @@ public final class ChannelMixingMatrix {
private final boolean isIdentity; private final boolean isIdentity;
/** /**
* Creates a standard channel mixing matrix that converts from {@code inputChannelCount} channels * Creates a basic channel mixing matrix that converts from {@code inputChannelCount} channels to
* to {@code outputChannelCount} channels. * {@code outputChannelCount} channels.
* *
* <p>If the input and output channel counts match then a simple identity matrix will be returned. * <p>If the input and output channel counts match then a simple identity matrix will be returned.
* Otherwise, default matrix coefficients will be used to best match channel locations and overall * Otherwise, default matrix coefficients will be used to best match channel locations and overall
@ -64,11 +65,32 @@ public final class ChannelMixingMatrix {
* @throws UnsupportedOperationException If no default matrix coefficients are implemented for the * @throws UnsupportedOperationException If no default matrix coefficients are implemented for the
* given input and output channel counts. * given input and output channel counts.
*/ */
// TODO(b/300467493): Modify create() to use constant power defaults and migrate all users.
public static ChannelMixingMatrix create(int inputChannelCount, int outputChannelCount) { public static ChannelMixingMatrix create(int inputChannelCount, int outputChannelCount) {
return new ChannelMixingMatrix( return new ChannelMixingMatrix(
inputChannelCount, inputChannelCount,
outputChannelCount, outputChannelCount,
createMixingCoefficients(inputChannelCount, outputChannelCount)); createConstantGainMixingCoefficients(inputChannelCount, outputChannelCount));
}
/**
* Returns default constant power matrix for mixing {@code inputChannelCount} channels into {@code
* outputChannelCount} channels.
*
* <p>If the input and output channel counts match then a simple identity matrix will be returned.
*
* @param inputChannelCount Number of input channels.
* @param outputChannelCount Number of output channels.
* @return New channel mixing matrix.
* @throws UnsupportedOperationException If no default coefficients are available for the given
* input and output channel counts.
*/
public static ChannelMixingMatrix createForConstantPower(
int inputChannelCount, int outputChannelCount) {
return new ChannelMixingMatrix(
inputChannelCount,
outputChannelCount,
createConstantPowerMixingCoefficients(inputChannelCount, outputChannelCount));
} }
/** /**
@ -155,26 +177,6 @@ public final class ChannelMixingMatrix {
return new ChannelMixingMatrix(inputChannelCount, outputChannelCount, scaledCoefficients); return new ChannelMixingMatrix(inputChannelCount, outputChannelCount, scaledCoefficients);
} }
private static float[] createMixingCoefficients(int inputChannelCount, int outputChannelCount) {
if (inputChannelCount == outputChannelCount) {
return initializeIdentityMatrix(outputChannelCount);
}
if (inputChannelCount == 1 && outputChannelCount == 2) {
// Mono -> stereo.
return new float[] {1f, 1f};
}
if (inputChannelCount == 2 && outputChannelCount == 1) {
// Stereo -> mono.
return new float[] {0.5f, 0.5f};
}
throw new UnsupportedOperationException(
"Default channel mixing coefficients for "
+ inputChannelCount
+ "->"
+ outputChannelCount
+ " are not yet implemented.");
}
private static float[] initializeIdentityMatrix(int channelCount) { private static float[] initializeIdentityMatrix(int channelCount) {
float[] coefficients = new float[channelCount * channelCount]; float[] coefficients = new float[channelCount * channelCount];
for (int c = 0; c < channelCount; c++) { for (int c = 0; c < channelCount; c++) {
@ -183,6 +185,7 @@ public final class ChannelMixingMatrix {
return coefficients; return coefficients;
} }
@CanIgnoreReturnValue
private static float[] checkCoefficientsValid(float[] coefficients) { private static float[] checkCoefficientsValid(float[] coefficients) {
for (int i = 0; i < coefficients.length; i++) { for (int i = 0; i < coefficients.length; i++) {
if (coefficients[i] < 0f) { if (coefficients[i] < 0f) {
@ -191,4 +194,137 @@ public final class ChannelMixingMatrix {
} }
return coefficients; return coefficients;
} }
private static float[] createConstantGainMixingCoefficients(
int inputChannelCount, int outputChannelCount) {
if (inputChannelCount == outputChannelCount) {
return initializeIdentityMatrix(outputChannelCount);
}
if (inputChannelCount == 1 && outputChannelCount == 2) {
// Mono -> stereo.
return new float[] {
1f, // left
1f // right
};
}
if (inputChannelCount == 2 && outputChannelCount == 1) {
// Stereo -> mono.
// 2 channels: [FRONT_LEFT, FRONT_RIGHT]
return new float[] {0.5f, 0.5f};
}
throw new UnsupportedOperationException(
"Default channel mixing coefficients for "
+ inputChannelCount
+ "->"
+ outputChannelCount
+ " are not yet implemented.");
}
private static float[] createConstantPowerMixingCoefficients(
int inputChannelCount, int outputChannelCount) {
if (outputChannelCount == 1) {
return getConstantPowerCoefficientsToMono(inputChannelCount);
}
if (outputChannelCount == 2) {
return getConstantPowerCoefficientsToStereo(inputChannelCount);
}
if (inputChannelCount == outputChannelCount) {
return initializeIdentityMatrix(outputChannelCount);
}
throw new UnsupportedOperationException(
"Default constant power channel mixing coefficients for "
+ inputChannelCount
+ "->"
+ outputChannelCount
+ " are not implemented.");
}
/**
* Returns a mixing coefficients float array from {@code inputChannelCount} to mono.
*
* <p>See <a
* href=https://cs.android.com/android/platform/superproject/main/+/main:system/media/audio_utils/include/audio_utils/ChannelMix.h;l=40;drc=13412bb94816e57e3a2de1018c65192f5b1a7261>android
* platform channel mixing calculations</a>.
*/
private static float[] getConstantPowerCoefficientsToMono(int inputChannelCount) {
switch (inputChannelCount) {
case 1:
// 1 channel: [MONO]
return new float[] {1.0f};
case 2:
// 2 channels: [FRONT_LEFT, FRONT_RIGHT]
return new float[] {0.7071f, 0.7071f};
case 3:
// 3 channels: [FRONT_LEFT, FRONT_RIGHT, FRONT_CENTER]
return new float[] {0.7071f, 0.7071f, 1.0f};
case 4:
// 4 channels: [FRONT_LEFT, FRONT_RIGHT, BACK_LEFT, BACK_RIGHT]
return new float[] {0.7071f, 0.7071f, 0.5f, 0.5f};
case 5:
// 5 channels: [FRONT_LEFT, FRONT_RIGHT, FRONT_CENTER, BACK_LEFT, BACK_RIGHT]
return new float[] {0.7071f, 0.7071f, 1.0f, 0.5f, 0.5f};
case 6:
// 6 channels: [FRONT_LEFT, FRONT_RIGHT, FRONT_CENTER, LOW_FREQUENCY, BACK_LEFT, BACK_RIGHT]
return new float[] {0.7071f, 0.7071f, 1.0f, 0.7071f, 0.5f, 0.5f};
default:
throw new UnsupportedOperationException(
"Default constant power channel mixing coefficients for "
+ inputChannelCount
+ "->1 are not implemented.");
}
}
/**
* Returns a mixing coefficients float array from {@code inputChannelCount} to stereo.
*
* <p>See <a
* href=https://cs.android.com/android/platform/superproject/main/+/main:system/media/audio_utils/include/audio_utils/ChannelMix.h;l=40;drc=13412bb94816e57e3a2de1018c65192f5b1a7261>android
* platform channel mixing calculations</a>.
*/
private static float[] getConstantPowerCoefficientsToStereo(int inputChannelCount) {
switch (inputChannelCount) {
case 1:
// 1 channel: [FRONT_CENTER]
return new float[] {
0.7071f, // left
0.7071f // right
};
case 2:
// 2 channels: [FRONT_LEFT, FRONT_RIGHT]
return new float[] {
/* left */ 1.0f, 0.0f,
/* right */ 0.0f, 1.0f
};
case 3:
// 3 channels: [FRONT_LEFT, FRONT_RIGHT, FRONT_CENTER]
return new float[] {
/* left */ 1.0f, 0.0f, 0.7071f,
/* right */ 0.0f, 1.0f, 0.7071f
};
case 4:
// 4 channels: [FRONT_LEFT, FRONT_RIGHT, BACK_LEFT, BACK_RIGHT]
return new float[] {
/* left */ 1.0f, 0.0f, 0.7071f, 0.0f,
/* right */ 0.0f, 1.0f, 0.0f, 0.7071f
};
case 5:
// 5 channels: [FRONT_LEFT, FRONT_RIGHT, FRONT_CENTER, BACK_LEFT, BACK_RIGHT]
return new float[] {
/* left */ 1.0f, 0.0f, 0.7071f, 0.7071f, 0.0f,
/* right */ 0.0f, 1.0f, 0.7071f, 0.0f, 0.7071f
};
case 6:
// 6 channels: [FRONT_LEFT, FRONT_RIGHT, FRONT_CENTER, LOW_FREQUENCY, BACK_LEFT, BACK_RIGHT]
return new float[] {
/* left */ 1.0f, 0.0f, 0.7071f, 0.5f, 0.7071f, 0.0f,
/* right */ 0.0f, 1.0f, 0.7071f, 0.5f, 0.0f, 0.7071f
};
default:
throw new UnsupportedOperationException(
"Default constant power channel mixing coefficients for "
+ inputChannelCount
+ "->2 are not implemented.");
}
}
} }