diff options
| -rw-r--r-- | api/current.txt | 5 | ||||
| -rw-r--r-- | api/system-current.txt | 5 | ||||
| -rw-r--r-- | core/jni/android_hardware_SoundTrigger.cpp | 2 | ||||
| -rw-r--r-- | core/jni/android_media_AudioTrack.cpp | 11 | ||||
| -rw-r--r-- | media/java/android/media/AudioFormat.java | 140 | ||||
| -rw-r--r-- | media/java/android/media/AudioTrack.java | 106 |
6 files changed, 236 insertions, 33 deletions
diff --git a/api/current.txt b/api/current.txt index 3cc9cc8e09ad..7d9c2957642c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -14406,6 +14406,8 @@ package android.media { } 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 @@ package android.media { 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 e1e07640a538..9e396a96968d 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -15603,6 +15603,8 @@ package android.media { } 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 @@ package android.media { 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 28e5030c11b5..d0c56f67b8d5 100644 --- a/core/jni/android_hardware_SoundTrigger.cpp +++ b/core/jni/android_hardware_SoundTrigger.cpp @@ -890,7 +890,7 @@ int register_android_hardware_SoundTrigger(JNIEnv *env) 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 5552245eb282..1f688e107fd1 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -204,9 +204,14 @@ android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, 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 3c973a20d1f3..ff6fed23dc2c 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 @@ public class AudioFormat { */ // 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 class AudioFormat { 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 @@ public class AudioFormat { 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 @@ public class AudioFormat { 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 @@ public class AudioFormat { mEncoding = af.mEncoding; mSampleRate = af.mSampleRate; mChannelMask = af.mChannelMask; + mChannelIndexMask = af.mChannelIndexMask; mPropertySetMask = af.mPropertySetMask; } @@ -397,6 +432,7 @@ public class AudioFormat { af.mEncoding = mEncoding; af.mSampleRate = mSampleRate; af.mChannelMask = mChannelMask; + af.mChannelIndexMask = mChannelIndexMask; af.mPropertySetMask = mPropertySetMask; return af; } @@ -437,29 +473,104 @@ public class AudioFormat { } /** - * 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 @@ public class AudioFormat { 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 4c5fb40bb701..9c6d640946b4 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -234,7 +234,7 @@ public class AudioTrack */ 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 @@ public class AudioTrack */ 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 @@ public class AudioTrack 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 @@ public class AudioTrack 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 @@ public class AudioTrack // 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 @@ public class AudioTrack 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 @@ public class AudioTrack 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 @@ public class AudioTrack } /** - * 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 @@ public class AudioTrack 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 { |