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 {