Add channel index mask to AudioTrack and AudioFormat
Change-Id: Ia5faa56360edcbbdeae8838ec0f82386f4e5e640
diff --git a/api/current.txt b/api/current.txt
index 3cc9cc8..7d9c295 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14406,6 +14406,8 @@
}
public class AudioFormat {
+ method public int getChannelCount();
+ method public int getChannelIndexMask();
method public int getChannelMask();
method public int getEncoding();
method public int getSampleRate();
@@ -14464,7 +14466,8 @@
ctor public AudioFormat.Builder();
ctor public AudioFormat.Builder(android.media.AudioFormat);
method public android.media.AudioFormat build();
- method public android.media.AudioFormat.Builder setChannelMask(int);
+ method public android.media.AudioFormat.Builder setChannelIndexMask(int) throws java.lang.IllegalArgumentException;
+ method public android.media.AudioFormat.Builder setChannelMask(int) throws java.lang.IllegalArgumentException;
method public android.media.AudioFormat.Builder setEncoding(int) throws java.lang.IllegalArgumentException;
method public android.media.AudioFormat.Builder setSampleRate(int) throws java.lang.IllegalArgumentException;
}
diff --git a/api/system-current.txt b/api/system-current.txt
index e1e0764..9e396a9 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -15603,6 +15603,8 @@
}
public class AudioFormat {
+ method public int getChannelCount();
+ method public int getChannelIndexMask();
method public int getChannelMask();
method public int getEncoding();
method public int getSampleRate();
@@ -15661,7 +15663,8 @@
ctor public AudioFormat.Builder();
ctor public AudioFormat.Builder(android.media.AudioFormat);
method public android.media.AudioFormat build();
- method public android.media.AudioFormat.Builder setChannelMask(int);
+ method public android.media.AudioFormat.Builder setChannelIndexMask(int) throws java.lang.IllegalArgumentException;
+ method public android.media.AudioFormat.Builder setChannelMask(int) throws java.lang.IllegalArgumentException;
method public android.media.AudioFormat.Builder setEncoding(int) throws java.lang.IllegalArgumentException;
method public android.media.AudioFormat.Builder setSampleRate(int) throws java.lang.IllegalArgumentException;
}
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
index 28e5030..d0c56f6 100644
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ b/core/jni/android_hardware_SoundTrigger.cpp
@@ -890,7 +890,7 @@
jclass audioFormatClass = FindClassOrDie(env, kAudioFormatClassPathName);
gAudioFormatClass = MakeGlobalRefOrDie(env, audioFormatClass);
- gAudioFormatCstor = GetMethodIDOrDie(env, audioFormatClass, "<init>", "(III)V");
+ gAudioFormatCstor = GetMethodIDOrDie(env, audioFormatClass, "<init>", "(IIII)V");
jclass soundModelEventClass = FindClassOrDie(env, kSoundModelEventClassPathName);
gSoundModelEventClass = MakeGlobalRefOrDie(env, soundModelEventClass);
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 5552245..1f688e1 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -204,9 +204,14 @@
return (jint) AUDIO_JAVA_ERROR;
}
- // Java channel masks don't map directly to the native definition, but it's a simple shift
- // to skip the two deprecated channel configurations "default" and "mono".
- audio_channel_mask_t nativeChannelMask = ((uint32_t)javaChannelMask) >> 2;
+ // Java channel masks don't map directly to the native definition for positional
+ // channel masks: it's a shift by 2 to skip the two deprecated channel
+ // configurations "default" and "mono".
+ // Invalid channel representations are caught by !audio_is_output_channel() below.
+ audio_channel_mask_t nativeChannelMask =
+ audio_channel_mask_get_representation(javaChannelMask)
+ == AUDIO_CHANNEL_REPRESENTATION_POSITION
+ ? javaChannelMask >> 2 : javaChannelMask;
if (!audio_is_output_channel(nativeChannelMask)) {
ALOGE("Error creating AudioTrack: invalid channel mask %#x.", javaChannelMask);
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 3c973a2..ff6fed2 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -17,7 +17,7 @@
package android.media;
import android.annotation.IntDef;
-
+import android.annotation.NonNull;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -286,13 +286,15 @@
*/
// Update sound trigger JNI in core/jni/android_hardware_SoundTrigger.cpp when modifying this
// constructor
- private AudioFormat(int encoding, int sampleRate, int channelMask) {
+ private AudioFormat(int encoding, int sampleRate, int channelMask, int channelIndexMask) {
mEncoding = encoding;
mSampleRate = sampleRate;
mChannelMask = channelMask;
+ mChannelIndexMask = channelIndexMask;
mPropertySetMask = AUDIO_FORMAT_HAS_PROPERTY_ENCODING |
AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE |
- AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK;
+ AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK |
+ AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK;
}
/** @hide */
@@ -303,10 +305,13 @@
public final static int AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE = 0x1 << 1;
/** @hide */
public final static int AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK = 0x1 << 2;
+ /** @hide */
+ public final static int AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK = 0x1 << 3;
private int mEncoding;
private int mSampleRate;
private int mChannelMask;
+ private int mChannelIndexMask;
private int mPropertySetMask;
/**
@@ -345,6 +350,34 @@
return mChannelMask;
}
+ /**
+ * Return the channel index mask.
+ * @return one of the values that can be set in {@link Builder#setChannelIndexMask(int)} or
+ * {@link AudioFormat#CHANNEL_INVALID} if not set or an invalid mask was used.
+ */
+ public int getChannelIndexMask() {
+ if ((mPropertySetMask & AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK) == 0) {
+ return CHANNEL_INVALID;
+ }
+ return mChannelIndexMask;
+ }
+
+ /**
+ * Return the channel count.
+ * @return the channel count derived from the channel position mask or the channel index mask.
+ * Zero is returned if both the channel position mask and the channel index mask are not set.
+ */
+ public int getChannelCount() {
+ final int channelIndexCount = Integer.bitCount(getChannelIndexMask());
+ int channelCount = channelCountFromOutChannelMask(getChannelMask());
+ if (channelCount == 0) {
+ channelCount = channelIndexCount;
+ } else if (channelCount != channelIndexCount && channelIndexCount != 0) {
+ channelCount = 0; // position and index channel count mismatch
+ }
+ return channelCount;
+ }
+
/** @hide */
public int getPropertySetMask() {
return mPropertySetMask;
@@ -368,6 +401,7 @@
private int mEncoding = ENCODING_INVALID;
private int mSampleRate = 0;
private int mChannelMask = CHANNEL_INVALID;
+ private int mChannelIndexMask = 0;
private int mPropertySetMask = AUDIO_FORMAT_HAS_PROPERTY_NONE;
/**
@@ -384,6 +418,7 @@
mEncoding = af.mEncoding;
mSampleRate = af.mSampleRate;
mChannelMask = af.mChannelMask;
+ mChannelIndexMask = af.mChannelIndexMask;
mPropertySetMask = af.mPropertySetMask;
}
@@ -397,6 +432,7 @@
af.mEncoding = mEncoding;
af.mSampleRate = mSampleRate;
af.mChannelMask = mChannelMask;
+ af.mChannelIndexMask = mChannelIndexMask;
af.mPropertySetMask = mPropertySetMask;
return af;
}
@@ -437,29 +473,104 @@
}
/**
- * Sets the channel mask.
+ * Sets the channel position mask.
+ * The channel position mask specifies the association between audio samples in a frame
+ * with named endpoint channels. The samples in the frame correspond to the
+ * named set bits in the channel position mask, in ascending bit order.
+ * See {@link #setChannelIndexMask(int)} to specify channels
+ * based on endpoint numbered channels.
* @param channelMask describes the configuration of the audio channels.
- * <p>For output, the mask should be a combination of
+ * <p> For output, the channelMask can be an OR-ed combination of
+ * channel position masks, e.g.
* {@link AudioFormat#CHANNEL_OUT_FRONT_LEFT},
- * {@link AudioFormat#CHANNEL_OUT_FRONT_CENTER},
* {@link AudioFormat#CHANNEL_OUT_FRONT_RIGHT},
- * {@link AudioFormat#CHANNEL_OUT_SIDE_LEFT},
- * {@link AudioFormat#CHANNEL_OUT_SIDE_RIGHT},
+ * {@link AudioFormat#CHANNEL_OUT_FRONT_CENTER},
+ * {@link AudioFormat#CHANNEL_OUT_LOW_FREQUENCY}
* {@link AudioFormat#CHANNEL_OUT_BACK_LEFT},
- * {@link AudioFormat#CHANNEL_OUT_BACK_RIGHT}.
- * <p>for input, the mask should be {@link AudioFormat#CHANNEL_IN_MONO} or
+ * {@link AudioFormat#CHANNEL_OUT_BACK_RIGHT},
+ * {@link AudioFormat#CHANNEL_OUT_BACK_CENTER},
+ * {@link AudioFormat#CHANNEL_OUT_SIDE_LEFT},
+ * {@link AudioFormat#CHANNEL_OUT_SIDE_RIGHT}.
+ * <p> For a valid {@link AudioTrack} channel position mask,
+ * the following conditions apply:
+ * <br> (1) at most eight channel positions may be used;
+ * <br> (2) right/left pairs should be matched.
+ * <p> For input or {@link AudioRecord}, the mask should be
+ * {@link AudioFormat#CHANNEL_IN_MONO} or
* {@link AudioFormat#CHANNEL_IN_STEREO}. {@link AudioFormat#CHANNEL_IN_MONO} is
* guaranteed to work on all devices.
- * @return the same Builder instance.
+ * @return the same <code>Builder</code> instance.
+ * @throws IllegalArgumentException if the channel mask is invalid or
+ * if both channel index mask and channel position mask
+ * are specified but do not have the same channel count.
*/
- public Builder setChannelMask(int channelMask) {
- // only validated when used, with input or output context
+ public @NonNull Builder setChannelMask(int channelMask) throws IllegalArgumentException {
+ if (channelMask == 0) {
+ throw new IllegalArgumentException("Invalid zero channel mask");
+ } else if (/* channelMask != 0 && */ mChannelIndexMask != 0 &&
+ Integer.bitCount(channelMask) != Integer.bitCount(mChannelIndexMask)) {
+ throw new IllegalArgumentException("Mismatched channel count for mask " +
+ Integer.toHexString(channelMask).toUpperCase());
+ }
mChannelMask = channelMask;
mPropertySetMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK;
return this;
}
/**
+ * Sets the channel index mask.
+ * A channel index mask specifies the association of audio samples in the frame
+ * with numbered endpoint channels. The i-th bit in the channel index
+ * mask corresponds to the i-th endpoint channel.
+ * For example, an endpoint with four channels is represented
+ * as index mask bits 0 through 3.
+ * See {@link #setChannelMask(int)} for a positional mask interpretation.
+ * <p> Both {@link AudioTrack} and {@link AudioRecord} support
+ * a channel index mask.
+ * If a channel index mask is specified it is used,
+ * otherwise the channel position mask specified
+ * by <code>setChannelMask</code> is used.
+ * For <code>AudioTrack</code> and <code>AudioRecord</code>,
+ * a channel position mask is not required if a channel index mask is specified.
+ *
+ * @param channelIndexMask describes the configuration of the audio channels.
+ * <p> For output, the <code>channelIndexMask</code> is an OR-ed combination of
+ * bits representing the mapping of <code>AudioTrack</code> write samples
+ * to output sink channels.
+ * For example, a mask of <code>0xa</code>, or binary <code>1010</code>,
+ * means the <code>AudioTrack</code> write frame consists of two samples,
+ * which are routed to the second and the fourth channels of the output sink.
+ * Unmatched output sink channels are zero filled and unmatched
+ * <code>AudioTrack</code> write samples are dropped.
+ * <p> For input, the <code>channelIndexMask</code> is an OR-ed combination of
+ * bits representing the mapping of input source channels to
+ * <code>AudioRecord</code> read samples.
+ * For example, a mask of <code>0x5</code>, or binary
+ * <code>101</code>, will read from the first and third channel of the input
+ * source device and store them in the first and second sample of the
+ * <code>AudioRecord</code> read frame.
+ * Unmatched input source channels are dropped and
+ * unmatched <code>AudioRecord</code> read samples are zero filled.
+ * @return the same <code>Builder</code> instance.
+ * @throws IllegalArgumentException if the channel index mask is invalid or
+ * if both channel index mask and channel position mask
+ * are specified but do not have the same channel count.
+ */
+ public @NonNull Builder setChannelIndexMask(int channelIndexMask)
+ throws IllegalArgumentException {
+ if (channelIndexMask == 0) {
+ throw new IllegalArgumentException("Invalid zero channel index mask");
+ } else if (/* channelIndexMask != 0 && */ mChannelMask != 0 &&
+ Integer.bitCount(channelIndexMask) != Integer.bitCount(mChannelMask)) {
+ throw new IllegalArgumentException("Mismatched channel count for index mask " +
+ Integer.toHexString(channelIndexMask).toUpperCase());
+ }
+ mChannelIndexMask = channelIndexMask;
+ mPropertySetMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK;
+ return this;
+ }
+
+ /**
* Sets the sample rate.
* @param sampleRate the sample rate expressed in Hz
* @return the same Builder instance.
@@ -480,7 +591,8 @@
return new String("AudioFormat:"
+ " props=" + mPropertySetMask
+ " enc=" + mEncoding
- + " chan=0x" + Integer.toHexString(mChannelMask)
+ + " chan=0x" + Integer.toHexString(mChannelMask).toUpperCase()
+ + " chan_index=0x" + Integer.toHexString(mChannelIndexMask).toUpperCase()
+ " rate=" + mSampleRate);
}
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 4c5fb40..9c6d640 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -234,7 +234,7 @@
*/
private int mChannelCount = 1;
/**
- * The audio channel mask.
+ * The audio channel mask used for calling native AudioTrack
*/
private int mChannels = AudioFormat.CHANNEL_OUT_MONO;
@@ -253,10 +253,16 @@
*/
private int mDataLoadMode = MODE_STREAM;
/**
- * The current audio channel configuration.
+ * The current channel position mask, as specified on AudioTrack creation.
+ * Can be set simultaneously with channel index mask {@link #mChannelIndexMask}.
+ * May be set to {@link AudioFormat#CHANNEL_INVALID} if a channel index mask is specified.
*/
private int mChannelConfiguration = AudioFormat.CHANNEL_OUT_MONO;
/**
+ * The current audio channel index configuration (if specified).
+ */
+ private int mChannelIndexMask = 0;
+ /**
* The encoding of the audio samples.
* @see AudioFormat#ENCODING_PCM_8BIT
* @see AudioFormat#ENCODING_PCM_16BIT
@@ -424,16 +430,24 @@
rate = 44100;
}
}
- int channelMask = AudioFormat.CHANNEL_OUT_FRONT_LEFT | AudioFormat.CHANNEL_OUT_FRONT_RIGHT;
- if ((format.getPropertySetMask() & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK) != 0)
- {
+ int channelIndexMask = 0;
+ if ((format.getPropertySetMask()
+ & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK) != 0) {
+ channelIndexMask = format.getChannelIndexMask();
+ }
+ int channelMask = 0;
+ if ((format.getPropertySetMask()
+ & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK) != 0) {
channelMask = format.getChannelMask();
+ } else if (channelIndexMask == 0) { // if no masks at all, use stereo
+ channelMask = AudioFormat.CHANNEL_OUT_FRONT_LEFT
+ | AudioFormat.CHANNEL_OUT_FRONT_RIGHT;
}
int encoding = AudioFormat.ENCODING_DEFAULT;
if ((format.getPropertySetMask() & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_ENCODING) != 0) {
encoding = format.getEncoding();
}
- audioParamCheck(rate, channelMask, encoding, mode);
+ audioParamCheck(rate, channelMask, channelIndexMask, encoding, mode);
mStreamType = AudioSystem.STREAM_DEFAULT;
audioBuffSizeCheck(bufferSizeInBytes);
@@ -653,6 +667,48 @@
AudioFormat.CHANNEL_OUT_SIDE_LEFT |
AudioFormat.CHANNEL_OUT_SIDE_RIGHT;
+ // Java channel mask definitions below match those
+ // in /system/core/include/system/audio.h in the JNI code of AudioTrack.
+
+ // internal maximum size for bits parameter, not part of public API
+ private static final int AUDIO_CHANNEL_BITS_LOG2 = 30;
+
+ // log(2) of maximum number of representations, not part of public API
+ private static final int AUDIO_CHANNEL_REPRESENTATION_LOG2 = 2;
+
+ // used to create a channel index mask or channel position mask
+ // with getChannelMaskFromRepresentationAndBits();
+ private static final int CHANNEL_OUT_REPRESENTATION_POSITION = 0;
+ private static final int CHANNEL_OUT_REPRESENTATION_INDEX = 2;
+
+ /**
+ * Return the channel mask from its representation and bits.
+ *
+ * This creates a channel mask for mChannels which combines a
+ * representation field and a bits field. This is for internal
+ * communication to native code, not part of the public API.
+ *
+ * @param representation the type of channel mask,
+ * either CHANNEL_OUT_REPRESENTATION_POSITION
+ * or CHANNEL_OUT_REPRESENTATION_INDEX
+ * @param bits is the channel bits specifying occupancy
+ * @return the channel mask
+ * @throws java.lang.IllegalArgumentException if representation is not recognized or
+ * the bits field is not acceptable for that representation
+ */
+ private static int getChannelMaskFromRepresentationAndBits(int representation, int bits) {
+ switch (representation) {
+ case CHANNEL_OUT_REPRESENTATION_POSITION:
+ case CHANNEL_OUT_REPRESENTATION_INDEX:
+ if ((bits & ~((1 << AUDIO_CHANNEL_BITS_LOG2) - 1)) != 0) {
+ throw new IllegalArgumentException("invalid bits " + bits);
+ }
+ return representation << AUDIO_CHANNEL_BITS_LOG2 | bits;
+ default:
+ throw new IllegalArgumentException("invalid representation " + representation);
+ }
+ }
+
// Convenience method for the constructor's parameter checks.
// This is where constructor IllegalArgumentException-s are thrown
// postconditions:
@@ -661,8 +717,8 @@
// mAudioFormat is valid
// mSampleRate is valid
// mDataLoadMode is valid
- private void audioParamCheck(int sampleRateInHz,
- int channelConfig, int audioFormat, int mode) {
+ private void audioParamCheck(int sampleRateInHz, int channelConfig, int channelIndexMask,
+ int audioFormat, int mode) {
//--------------
// sample rate, note these values are subject to change
if (sampleRateInHz < SAMPLE_RATE_HZ_MIN || sampleRateInHz > SAMPLE_RATE_HZ_MAX) {
@@ -688,6 +744,10 @@
mChannels = AudioFormat.CHANNEL_OUT_STEREO;
break;
default:
+ if (channelConfig == AudioFormat.CHANNEL_INVALID && channelIndexMask != 0) {
+ mChannelCount = 0;
+ break; // channel index configuration only
+ }
if (!isMultichannelConfigSupported(channelConfig)) {
// input channel configuration features unsupported channels
throw new IllegalArgumentException("Unsupported channel configuration.");
@@ -695,6 +755,27 @@
mChannels = channelConfig;
mChannelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig);
}
+ // check the channel index configuration (if present)
+ mChannelIndexMask = channelIndexMask;
+ if (mChannelIndexMask != 0) {
+ // restrictive: indexMask could allow up to AUDIO_CHANNEL_BITS_LOG2
+ final int indexMask = (1 << CHANNEL_COUNT_MAX) - 1;
+ if ((channelIndexMask & ~indexMask) != 0) {
+ throw new IllegalArgumentException("Unsupported channel index configuration "
+ + channelIndexMask);
+ }
+ int channelIndexCount = Integer.bitCount(channelIndexMask);
+ if (mChannelCount == 0) {
+ mChannelCount = channelIndexCount;
+ } else if (mChannelCount != channelIndexCount) {
+ throw new IllegalArgumentException("Channel count must match");
+ }
+
+ // AudioTrack prefers to use the channel index configuration
+ // over the channel position configuration if both are specified.
+ mChannels = getChannelMaskFromRepresentationAndBits(
+ CHANNEL_OUT_REPRESENTATION_INDEX, mChannelIndexMask);
+ }
//--------------
// audio format
@@ -865,9 +946,9 @@
}
/**
- * Returns the configured channel configuration.
- * See {@link AudioFormat#CHANNEL_OUT_MONO}
- * and {@link AudioFormat#CHANNEL_OUT_STEREO}.
+ * Returns the configured channel position mask.
+ * For example, refer to {@link AudioFormat#CHANNEL_OUT_MONO},
+ * {@link AudioFormat#CHANNEL_OUT_STEREO}, {@link AudioFormat#CHANNEL_OUT_5POINT1}.
*/
public int getChannelConfiguration() {
return mChannelConfiguration;
@@ -1004,8 +1085,7 @@
channelCount = 2;
break;
default:
- if ((channelConfig & SUPPORTED_OUT_CHANNELS) != channelConfig) {
- // input channel configuration features unsupported channels
+ if (!isMultichannelConfigSupported(channelConfig)) {
loge("getMinBufferSize(): Invalid channel configuration.");
return ERROR_BAD_VALUE;
} else {