diff --git a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp index eaf3ac041..6706698a4 100644 --- a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp +++ b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp @@ -145,6 +145,7 @@ void ActivityContext::configureBuilder(bool isInput, oboe::AudioStreamBuilder &b int ActivityContext::open(jint nativeApi, jint sampleRate, jint channelCount, + jint channelMask, jint format, jint sharingMode, jint performanceMode, @@ -199,6 +200,10 @@ int ActivityContext::open(jint nativeApi, ->setFormatConversionAllowed(formatConversionAllowed) ->setSampleRateConversionQuality((oboe::SampleRateConversionQuality) rateConversionQuality) ; + if (channelMask != (jint) oboe::ChannelMask::Unspecified) { + // Set channel mask when it is specified. + builder.setChannelMask((oboe::ChannelMask) channelMask); + } if (mUseCallback) { builder.setFramesPerCallback(callbackSize); } diff --git a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h index 0c6a3dd55..73710bd9c 100644 --- a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h +++ b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h @@ -59,7 +59,7 @@ #define NATIVE_MODE_OPENSLES 1 #define NATIVE_MODE_AAUDIO 2 -#define MAX_SINE_OSCILLATORS 8 +#define MAX_SINE_OSCILLATORS 16 #define AMPLITUDE_SINE 1.0 #define AMPLITUDE_SAWTOOTH 0.5 #define FREQUENCY_SAW_PING 800.0 @@ -98,6 +98,7 @@ class ActivityContext { * @param nativeApi * @param sampleRate * @param channelCount + * @param channelMask * @param format * @param sharingMode * @param performanceMode @@ -115,6 +116,7 @@ class ActivityContext { int open(jint nativeApi, jint sampleRate, jint channelCount, + jint channelMask, jint format, jint sharingMode, jint performanceMode, diff --git a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp index e5c58a893..63f407555 100644 --- a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp +++ b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp @@ -39,6 +39,7 @@ Java_com_mobileer_oboetester_OboeAudioStream_openNative(JNIEnv *env, jobject, jint nativeApi, jint sampleRate, jint channelCount, + jint channelMask, jint format, jint sharingMode, jint performanceMode, @@ -117,6 +118,7 @@ Java_com_mobileer_oboetester_OboeAudioStream_openNative( jint nativeApi, jint sampleRate, jint channelCount, + jint channelMask, jint format, jint sharingMode, jint performanceMode, @@ -136,6 +138,7 @@ Java_com_mobileer_oboetester_OboeAudioStream_openNative( return (jint) engine.getCurrentActivity()->open(nativeApi, sampleRate, channelCount, + channelMask, format, sharingMode, performanceMode, @@ -309,6 +312,17 @@ Java_com_mobileer_oboetester_OboeAudioStream_getChannelCount( return result; } +JNIEXPORT jint JNICALL +Java_com_mobileer_oboetester_OboeAudioStream_getChannelMask( + JNIEnv *env, jobject, jint streamIndex) { + jint result = (jint) oboe::Result::ErrorNull; + std::shared_ptr oboeStream = engine.getCurrentActivity()->getStream(streamIndex); + if (oboeStream != nullptr) { + result = (jint) oboeStream->getChannelMask(); + } + return result; +} + JNIEXPORT jint JNICALL Java_com_mobileer_oboetester_OboeAudioStream_getFormat(JNIEnv *env, jobject instance, jint streamIndex) { jint result = (jint) oboe::Result::ErrorNull; diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/IntentBasedTestSupport.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/IntentBasedTestSupport.java index 6aef5ab25..580c3dbcf 100644 --- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/IntentBasedTestSupport.java +++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/IntentBasedTestSupport.java @@ -67,6 +67,33 @@ public class IntentBasedTestSupport { public static final String VALUE_VOLUME_TYPE_SYSTEM = "system"; public static final String VALUE_VOLUME_TYPE_VOICE_CALL = "voice_call"; + public static final String KEY_CHANNEL_MASK = "channel_mask"; + public static final String VALUE_CHANNEL_MONO = "mono"; + public static final String VALUE_CHANNEL_STEREO = "stereo"; + public static final String VALUE_CHANNEL_2POINT1 = "2.1"; + public static final String VALUE_CHANNEL_TRI = "tri"; + public static final String VALUE_CHANNEL_TRI_BACK = "triBack"; + public static final String VALUE_CHANNEL_3POINT1 = "3.1"; + public static final String VALUE_CHANNEL_2POINT0POINT2 = "2.0.2"; + public static final String VALUE_CHANNEL_2POINT1POINT2 = "2.1.2"; + public static final String VALUE_CHANNEL_3POINT0POINT2 = "3.0.2"; + public static final String VALUE_CHANNEL_3POINT1POINT2 = "3.1.2"; + public static final String VALUE_CHANNEL_QUAD = "quad"; + public static final String VALUE_CHANNEL_QUAD_SIDE = "quadside"; + public static final String VALUE_CHANNEL_SURROUND = "surround"; + public static final String VALUE_CHANNEL_PENTA = "penta"; + public static final String VALUE_CHANNEL_5POINT1 = "5.1"; + public static final String VALUE_CHANNEL_5POINT1_SIDE = "5.1side"; + public static final String VALUE_CHANNEL_6POINT1 = "6.1"; + public static final String VALUE_CHANNEL_7POINT1 = "7.1"; + public static final String VALUE_CHANNEL_5POINT1POINT2 = "5.1.2"; + public static final String VALUE_CHANNEL_5POINT1POINT4 = "5.1.4"; + public static final String VALUE_CHANNEL_7POINT1POINT2 = "7.1.2"; + public static final String VALUE_CHANNEL_7POINT1POINT4 = "7.1.4"; + public static final String VALUE_CHANNEL_9POINT1POINT4 = "9.1.4"; + public static final String VALUE_CHANNEL_9POINT1POINT6 = "9.1.6"; + public static final String VALUE_CHANNEL_FRONT_BACK = "frontBack"; + public static int getApiFromText(String text) { if (VALUE_API_AAUDIO.equals(text)) { return StreamConfiguration.NATIVE_API_AAUDIO; @@ -136,6 +163,68 @@ public static int getVolumeStreamTypeFromBundle(Bundle bundle) { } } + public static int getChannelMaskFromBundle(Bundle bundle) { + String channelMaskText = bundle.getString(KEY_CHANNEL_MASK); + if (channelMaskText == null) { + return StreamConfiguration.UNSPECIFIED; + } + switch (channelMaskText) { + case VALUE_CHANNEL_MONO: + return StreamConfiguration.CHANNEL_MONO; + case VALUE_CHANNEL_STEREO: + return StreamConfiguration.CHANNEL_STEREO; + case VALUE_CHANNEL_2POINT1: + return StreamConfiguration.CHANNEL_2POINT1; + case VALUE_CHANNEL_TRI: + return StreamConfiguration.CHANNEL_TRI; + case VALUE_CHANNEL_TRI_BACK: + return StreamConfiguration.CHANNEL_TRI_BACK; + case VALUE_CHANNEL_3POINT1: + return StreamConfiguration.CHANNEL_3POINT1; + case VALUE_CHANNEL_2POINT0POINT2: + return StreamConfiguration.CHANNEL_2POINT0POINT2; + case VALUE_CHANNEL_2POINT1POINT2: + return StreamConfiguration.CHANNEL_2POINT1POINT2; + case VALUE_CHANNEL_3POINT0POINT2: + return StreamConfiguration.CHANNEL_3POINT0POINT2; + case VALUE_CHANNEL_3POINT1POINT2: + return StreamConfiguration.CHANNEL_3POINT1POINT2; + case VALUE_CHANNEL_QUAD: + return StreamConfiguration.CHANNEL_QUAD; + case VALUE_CHANNEL_QUAD_SIDE: + return StreamConfiguration.CHANNEL_QUAD_SIDE; + case VALUE_CHANNEL_SURROUND: + return StreamConfiguration.CHANNEL_SURROUND; + case VALUE_CHANNEL_PENTA: + return StreamConfiguration.CHANNEL_PENTA; + case VALUE_CHANNEL_5POINT1: + return StreamConfiguration.CHANNEL_5POINT1; + case VALUE_CHANNEL_5POINT1_SIDE: + return StreamConfiguration.CHANNEL_5POINT1_SIDE; + case VALUE_CHANNEL_6POINT1: + return StreamConfiguration.CHANNEL_6POINT1; + case VALUE_CHANNEL_7POINT1: + return StreamConfiguration.CHANNEL_7POINT1; + case VALUE_CHANNEL_5POINT1POINT2: + return StreamConfiguration.CHANNEL_5POINT1POINT2; + case VALUE_CHANNEL_5POINT1POINT4: + return StreamConfiguration.CHANNEL_5POINT1POINT4; + case VALUE_CHANNEL_7POINT1POINT2: + return StreamConfiguration.CHANNEL_7POINT1POINT2; + case VALUE_CHANNEL_7POINT1POINT4: + return StreamConfiguration.CHANNEL_7POINT1POINT4; + case VALUE_CHANNEL_9POINT1POINT4: + return StreamConfiguration.CHANNEL_9POINT1POINT4; + case VALUE_CHANNEL_9POINT1POINT6: + return StreamConfiguration.CHANNEL_9POINT1POINT6; + case VALUE_CHANNEL_FRONT_BACK: + return StreamConfiguration.CHANNEL_FRONT_BACK; + default: + throw new IllegalArgumentException( + KEY_CHANNEL_MASK + " invalid: " + channelMaskText); + } + } + public static void configureOutputStreamFromBundle(Bundle bundle, StreamConfiguration requestedOutConfig) { int audioApi; @@ -151,7 +240,13 @@ public static void configureOutputStreamFromBundle(Bundle bundle, requestedOutConfig.setNativeApi(audioApi); int outChannels = bundle.getInt(KEY_OUT_CHANNELS, VALUE_DEFAULT_CHANNELS); - requestedOutConfig.setChannelCount(outChannels); + int channelMask = getChannelMaskFromBundle(bundle); + // Respect channel mask when it is specified. + if (channelMask != StreamConfiguration.UNSPECIFIED) { + requestedOutConfig.setChannelMask(channelMask); + } else { + requestedOutConfig.setChannelCount(outChannels); + } boolean outMMAP = bundle.getBoolean(KEY_OUT_USE_MMAP, VALUE_DEFAULT_USE_MMAP); requestedOutConfig.setMMap(outMMAP); @@ -181,7 +276,13 @@ public static void configureInputStreamFromBundle(Bundle bundle, requestedInConfig.setNativeApi(audioApi); int inChannels = bundle.getInt(KEY_IN_CHANNELS, VALUE_DEFAULT_CHANNELS); - requestedInConfig.setChannelCount(inChannels); + int channelMask = getChannelMaskFromBundle(bundle); + // Respect channel mask when it is specified. + if (channelMask != StreamConfiguration.UNSPECIFIED) { + requestedInConfig.setChannelMask(channelMask); + } else { + requestedInConfig.setChannelCount(inChannels); + } boolean inMMAP = bundle.getBoolean(KEY_IN_USE_MMAP, VALUE_DEFAULT_USE_MMAP); requestedInConfig.setMMap(inMMAP); diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/OboeAudioStream.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/OboeAudioStream.java index 0193d8240..6118e927c 100644 --- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/OboeAudioStream.java +++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/OboeAudioStream.java @@ -59,6 +59,7 @@ public void open(StreamConfiguration requestedConfiguration, int result = openNative(requestedConfiguration.getNativeApi(), requestedConfiguration.getSampleRate(), requestedConfiguration.getChannelCount(), + requestedConfiguration.getChannelMask(), requestedConfiguration.getFormat(), requestedConfiguration.getSharingMode(), requestedConfiguration.getPerformanceMode(), @@ -90,6 +91,7 @@ public void open(StreamConfiguration requestedConfiguration, actualConfiguration.setFramesPerBurst(getFramesPerBurst()); actualConfiguration.setBufferCapacityInFrames(getBufferCapacityInFrames()); actualConfiguration.setChannelCount(getChannelCount()); + actualConfiguration.setChannelMask(getChannelMask()); actualConfiguration.setDeviceId(getDeviceId()); actualConfiguration.setSessionId(getSessionId()); actualConfiguration.setFormat(getFormat()); @@ -103,6 +105,7 @@ private native int openNative( int nativeApi, int sampleRate, int channelCount, + int channelMask, int format, int sharingMode, int performanceMode, @@ -201,6 +204,11 @@ public int getChannelCount() { } public native int getChannelCount(int streamIndex); + public int getChannelMask() { + return getChannelMask(streamIndex); + } + public native int getChannelMask(int streamIndex); + public int getDeviceId() { return getDeviceId(streamIndex); } diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfiguration.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfiguration.java index cfeb69723..ea9a9e8cc 100644 --- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfiguration.java +++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfiguration.java @@ -18,7 +18,9 @@ import android.content.res.Resources; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; /** * Container for the properties of a Stream. @@ -90,6 +92,119 @@ public class StreamConfiguration { public static final int CONTENT_TYPE_MOVIE = 3; public static final int CONTENT_TYPE_SONIFICATION = 4; + public static final int CHANNEL_FRONT_LEFT = 1 << 0; + public static final int CHANNEL_FRONT_RIGHT = 1 << 1; + public static final int CHANNEL_FRONT_CENTER = 1 << 2; + public static final int CHANNEL_LOW_FREQUENCY = 1 << 3; + public static final int CHANNEL_BACK_LEFT = 1 << 4; + public static final int CHANNEL_BACK_RIGHT = 1 << 5; + public static final int CHANNEL_FRONT_LEFT_OF_CENTER = 1 << 6; + public static final int CHANNEL_FRONT_RIGHT_OF_CENTER = 1 << 7; + public static final int CHANNEL_BACK_CENTER = 1 << 8; + public static final int CHANNEL_SIDE_LEFT = 1 << 9; + public static final int CHANNEL_SIDE_RIGHT = 1 << 10; + public static final int CHANNEL_TOP_CENTER = 1 << 11; + public static final int CHANNEL_TOP_FRONT_LEFT = 1 << 12; + public static final int CHANNEL_TOP_FRONT_CENTER = 1 << 13; + public static final int CHANNEL_TOP_FRONT_RIGHT = 1 << 14; + public static final int CHANNEL_TOP_BACK_LEFT = 1 << 15; + public static final int CHANNEL_TOP_BACK_CENTER = 1 << 16; + public static final int CHANNEL_TOP_BACK_RIGHT = 1 << 17; + public static final int CHANNEL_TOP_SIDE_LEFT = 1 << 18; + public static final int CHANNEL_TOP_SIDE_RIGHT = 1 << 19; + public static final int CHANNEL_BOTTOM_FRONT_LEFT = 1 << 20; + public static final int CHANNEL_BOTTOM_FRONT_CENTER = 1 << 21; + public static final int CHANNEL_BOTTOM_FRONT_RIGHT = 1 << 22; + public static final int CHANNEL_LOW_FREQUENCY_2 = 1 << 23; + public static final int CHANNEL_FRONT_WIDE_LEFT = 1 << 24; + public static final int CHANNEL_FRONT_WIDE_RIGHT = 1 << 25; + + public static final int CHANNEL_MONO = CHANNEL_FRONT_LEFT; + public static final int CHANNEL_STEREO = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT; + public static final int CHANNEL_2POINT1 = CHANNEL_FRONT_LEFT | + CHANNEL_FRONT_RIGHT | + CHANNEL_LOW_FREQUENCY; + public static final int CHANNEL_TRI = CHANNEL_FRONT_LEFT | + CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER; + public static final int CHANNEL_TRI_BACK = CHANNEL_FRONT_LEFT | + CHANNEL_FRONT_RIGHT | + CHANNEL_BACK_CENTER; + public static final int CHANNEL_3POINT1 = CHANNEL_FRONT_LEFT | + CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | + CHANNEL_LOW_FREQUENCY; + public static final int CHANNEL_2POINT0POINT2 = CHANNEL_FRONT_LEFT | + CHANNEL_FRONT_RIGHT | + CHANNEL_TOP_SIDE_LEFT | + CHANNEL_TOP_SIDE_RIGHT; + public static final int CHANNEL_2POINT1POINT2 = CHANNEL_2POINT0POINT2 | CHANNEL_LOW_FREQUENCY; + public static final int CHANNEL_3POINT0POINT2 = CHANNEL_FRONT_LEFT | + CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | + CHANNEL_TOP_SIDE_LEFT | + CHANNEL_TOP_SIDE_RIGHT; + public static final int CHANNEL_3POINT1POINT2 = CHANNEL_3POINT0POINT2 | CHANNEL_LOW_FREQUENCY; + public static final int CHANNEL_QUAD = CHANNEL_FRONT_LEFT | + CHANNEL_FRONT_RIGHT | + CHANNEL_BACK_LEFT | + CHANNEL_BACK_RIGHT; + public static final int CHANNEL_QUAD_SIDE = CHANNEL_FRONT_LEFT | + CHANNEL_FRONT_RIGHT | + CHANNEL_SIDE_LEFT | + CHANNEL_SIDE_RIGHT; + public static final int CHANNEL_SURROUND = CHANNEL_FRONT_LEFT | + CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | + CHANNEL_BACK_CENTER; + public static final int CHANNEL_PENTA = CHANNEL_QUAD | CHANNEL_FRONT_CENTER; + // aka 5POINT1_BACK + public static final int CHANNEL_5POINT1 = CHANNEL_FRONT_LEFT | + CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | + CHANNEL_LOW_FREQUENCY | + CHANNEL_BACK_LEFT | + CHANNEL_BACK_RIGHT; + public static final int CHANNEL_5POINT1_SIDE = CHANNEL_FRONT_LEFT | + CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | + CHANNEL_LOW_FREQUENCY | + CHANNEL_SIDE_LEFT | + CHANNEL_SIDE_RIGHT; + public static final int CHANNEL_6POINT1 = CHANNEL_FRONT_LEFT | + CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | + CHANNEL_LOW_FREQUENCY | + CHANNEL_BACK_LEFT | + CHANNEL_BACK_RIGHT | + CHANNEL_BACK_CENTER; + public static final int CHANNEL_7POINT1 = CHANNEL_5POINT1 | + CHANNEL_SIDE_LEFT | + CHANNEL_SIDE_RIGHT; + public static final int CHANNEL_5POINT1POINT2 = CHANNEL_5POINT1 | + CHANNEL_TOP_SIDE_LEFT | + CHANNEL_TOP_SIDE_RIGHT; + public static final int CHANNEL_5POINT1POINT4 = CHANNEL_5POINT1 | + CHANNEL_TOP_FRONT_LEFT | + CHANNEL_TOP_FRONT_RIGHT | + CHANNEL_TOP_BACK_LEFT | + CHANNEL_TOP_BACK_RIGHT; + public static final int CHANNEL_7POINT1POINT2 = CHANNEL_7POINT1 | + CHANNEL_TOP_SIDE_LEFT | + CHANNEL_TOP_SIDE_RIGHT; + public static final int CHANNEL_7POINT1POINT4 = CHANNEL_7POINT1 | + CHANNEL_TOP_FRONT_LEFT | + CHANNEL_TOP_FRONT_RIGHT | + CHANNEL_TOP_BACK_LEFT | + CHANNEL_TOP_BACK_RIGHT; + public static final int CHANNEL_9POINT1POINT4 = CHANNEL_7POINT1POINT4 | + CHANNEL_FRONT_WIDE_LEFT | + CHANNEL_FRONT_WIDE_RIGHT; + public static final int CHANNEL_9POINT1POINT6 = CHANNEL_9POINT1POINT4 | + CHANNEL_TOP_SIDE_LEFT | + CHANNEL_TOP_SIDE_RIGHT; + public static final int CHANNEL_FRONT_BACK = CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER; + public static final int[] usages = { USAGE_MEDIA, USAGE_VOICE_COMMUNICATION, @@ -110,8 +225,38 @@ public class StreamConfiguration { CONTENT_TYPE_MOVIE, CONTENT_TYPE_SONIFICATION}; + public static final int[] channelMasks = { + CHANNEL_MONO, + CHANNEL_STEREO, + CHANNEL_2POINT1, + CHANNEL_TRI, + CHANNEL_TRI_BACK, + CHANNEL_3POINT1, + CHANNEL_2POINT0POINT2, + CHANNEL_2POINT1POINT2, + CHANNEL_3POINT0POINT2, + CHANNEL_3POINT1POINT2, + CHANNEL_QUAD, + CHANNEL_QUAD_SIDE, + CHANNEL_SURROUND, + CHANNEL_PENTA, + CHANNEL_5POINT1, + CHANNEL_5POINT1_SIDE, + CHANNEL_6POINT1, + CHANNEL_7POINT1, + CHANNEL_5POINT1POINT2, + CHANNEL_5POINT1POINT4, + CHANNEL_7POINT1POINT2, + CHANNEL_7POINT1POINT4, + CHANNEL_9POINT1POINT4, + CHANNEL_9POINT1POINT6, + CHANNEL_FRONT_BACK + }; + private static HashMap mUsageStringToIntegerMap; private static HashMap mContentTypeStringToIntegerMap; + private static HashMap mChannelMaskStringToIntegerMap; + private static List mChannelMaskStrings = new ArrayList<>(); private int mNativeApi; private int mBufferCapacityInFrames; @@ -131,6 +276,7 @@ public class StreamConfiguration { private int mContentType; private int mFramesPerBurst; private boolean mMMap; + private int mChannelMask; public StreamConfiguration() { reset(); @@ -150,12 +296,24 @@ public StreamConfiguration() { for (int contentType : contentTypes) { mContentTypeStringToIntegerMap.put(convertContentTypeToText(contentType), contentType); } + + // Build map for Channel Mask string-to-int conversion. + mChannelMaskStringToIntegerMap = new HashMap(); + String channelMaskStr = convertChannelMaskToText(UNSPECIFIED); + mChannelMaskStringToIntegerMap.put(channelMaskStr, UNSPECIFIED); + mChannelMaskStrings.add(channelMaskStr); + for (int channelMask : channelMasks) { + channelMaskStr = convertChannelMaskToText(channelMask); + mChannelMaskStringToIntegerMap.put(channelMaskStr, channelMask); + mChannelMaskStrings.add(channelMaskStr); + } } public void reset() { mNativeApi = NATIVE_API_UNSPECIFIED; mBufferCapacityInFrames = UNSPECIFIED; mChannelCount = UNSPECIFIED; + mChannelMask = UNSPECIFIED; mDeviceId = UNSPECIFIED; mSessionId = -1; mFormat = AUDIO_FORMAT_PCM_FLOAT; @@ -346,6 +504,69 @@ public static String convertNativeApiToText(int api) { } } + public static String convertChannelMaskToText(int channelMask) { + switch (channelMask) { + case UNSPECIFIED: + return "Unspecified"; + case CHANNEL_MONO: + return "Mono"; + case CHANNEL_STEREO: + return "Stereo"; + case CHANNEL_2POINT1: + return "2.1"; + case CHANNEL_TRI: + return "Tri"; + case CHANNEL_TRI_BACK: + return "TriBack"; + case CHANNEL_3POINT1: + return "3.1"; + case CHANNEL_2POINT0POINT2: + return "2.0.2"; + case CHANNEL_2POINT1POINT2: + return "2.1.2"; + case CHANNEL_3POINT0POINT2: + return "3.0.2"; + case CHANNEL_3POINT1POINT2: + return "3.1.2"; + case CHANNEL_QUAD: + return "Quad"; + case CHANNEL_QUAD_SIDE: + return "QuadSide"; + case CHANNEL_SURROUND: + return "Surround"; + case CHANNEL_PENTA: + return "Penta"; + case CHANNEL_5POINT1: + return "5.1"; + case CHANNEL_5POINT1_SIDE: + return "5.1Side"; + case CHANNEL_6POINT1: + return "6.1"; + case CHANNEL_7POINT1: + return "7.1"; + case CHANNEL_5POINT1POINT2: + return "5.1.2"; + case CHANNEL_5POINT1POINT4: + return "5.1.4"; + case CHANNEL_7POINT1POINT2: + return "7.1.2"; + case CHANNEL_7POINT1POINT4: + return "7.1.4"; + case CHANNEL_9POINT1POINT4: + return "9.1.4"; + case CHANNEL_9POINT1POINT6: + return "9.1.6"; + case CHANNEL_FRONT_BACK: + return "FrontBack"; + default: + return "?=" + Integer.toHexString(channelMask); + } + } + + public static int convertTextToChannelMask(String text) { + return mChannelMaskStringToIntegerMap.get(text); + } + public String dump() { String prefix = (getDirection() == DIRECTION_INPUT) ? "in" : "out"; @@ -493,4 +714,16 @@ public int getRateConversionQuality() { return mRateConversionQuality; } + public int getChannelMask() { + return mChannelMask; + } + + public void setChannelMask(int channelMask) { + this.mChannelMask = channelMask; + } + + public static List getAllChannelMasks() { + return mChannelMaskStrings; + } + } diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfigurationView.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfigurationView.java index 5a53d1106..c7973a597 100644 --- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfigurationView.java +++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfigurationView.java @@ -22,11 +22,13 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.AdapterView; +import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.Spinner; import android.widget.TableRow; import android.widget.TextView; import android.widget.LinearLayout; +import android.util.Log; import com.mobileer.audio_device.AudioDeviceListEntry; import com.mobileer.audio_device.AudioDeviceSpinner; @@ -37,6 +39,8 @@ */ public class StreamConfigurationView extends LinearLayout { + private static final String TAG = "StreamConfigurationView"; + protected Spinner mNativeApiSpinner; private TextView mActualNativeApiView; @@ -50,6 +54,8 @@ public class StreamConfigurationView extends LinearLayout { private CheckBox mFormatConversionBox; private Spinner mChannelCountSpinner; private TextView mActualChannelCountView; + private Spinner mChannelMaskSpinner; + private TextView mActualChannelMaskView; private TextView mActualFormatView; private TableRow mInputPresetTableRow; @@ -80,6 +86,8 @@ public class StreamConfigurationView extends LinearLayout { private String mHideSettingsText; private String mShowSettingsText; + private boolean mIsChannelMaskLastSelected; + // Create an anonymous implementation of OnClickListener private View.OnClickListener mToggleListener = new View.OnClickListener() { public void onClick(View v) { @@ -184,6 +192,17 @@ private void initializeViews(Context context) { mSampleRateSpinner = (Spinner) findViewById(R.id.spinnerSampleRate); mActualChannelCountView = (TextView) findViewById(R.id.actualChannelCount); mChannelCountSpinner = (Spinner) findViewById(R.id.spinnerChannelCount); + mChannelCountSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + onChannelCountSpinnerSelected(); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + // no-op + } + }); mActualFormatView = (TextView) findViewById(R.id.actualAudioFormat); mFormatSpinner = (Spinner) findViewById(R.id.spinnerFormat); mRateConversionQualitySpinner = (Spinner) findViewById(R.id.spinnerSRCQuality); @@ -212,6 +231,24 @@ private void initializeViews(Context context) { mDeviceSpinner = (AudioDeviceSpinner) findViewById(R.id.devices_spinner); + mActualChannelMaskView = (TextView) findViewById(R.id.actualChannelMask); + mChannelMaskSpinner = (Spinner) findViewById(R.id.spinnerChannelMask); + ArrayAdapter channelMaskSpinnerArrayAdapter = new ArrayAdapter(context, + android.R.layout.simple_spinner_item, + StreamConfiguration.getAllChannelMasks()); + mChannelMaskSpinner.setAdapter(channelMaskSpinnerArrayAdapter); + mChannelMaskSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + onChannelMaskSpinnerSelected(); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + // no-op + } + }); + showSettingsView(); } @@ -238,7 +275,6 @@ public void setOutput(boolean output) { public void applyToModel(StreamConfiguration config) { // Menu position matches actual enum value for these properties. config.setNativeApi(mNativeApiSpinner.getSelectedItemPosition()); - config.setChannelCount(mChannelCountSpinner.getSelectedItemPosition()); config.setFormat(mFormatSpinner.getSelectedItemPosition()); config.setRateConversionQuality(mRateConversionQualitySpinner.getSelectedItemPosition()); @@ -261,6 +297,20 @@ public void applyToModel(StreamConfiguration config) { int contentType = StreamConfiguration.convertTextToContentType(text); config.setContentType(contentType); + // The corresponding channel count of the selected channel mask may be different from + // the selected channel count, the last selected will be respected. + if (mIsChannelMaskLastSelected) { + text = mChannelMaskSpinner.getSelectedItem().toString(); + int channelMask = StreamConfiguration.convertTextToChannelMask(text); + config.setChannelMask(channelMask); + config.setChannelCount(0); + Log.d(TAG, String.format("Set channel mask as %s(%#x)", text, channelMask)); + } else { + config.setChannelCount(mChannelCountSpinner.getSelectedItemPosition()); + config.setChannelMask(StreamConfiguration.UNSPECIFIED); + Log.d(TAG, "Set channel count as " + mChannelCountSpinner.getSelectedItemPosition()); + } + config.setMMap(mRequestedMMapView.isChecked()); config.setChannelConversionAllowed(mChannelConversionBox.isChecked()); config.setFormatConversionAllowed(mFormatConversionBox.isChecked()); @@ -283,6 +333,7 @@ public void setChildrenEnabled(boolean enabled) { mChannelConversionBox.setEnabled(enabled); mFormatConversionBox.setEnabled(enabled); mChannelCountSpinner.setEnabled(enabled); + mChannelMaskSpinner.setEnabled(enabled); mInputPresetSpinner.setEnabled(enabled); mUsageSpinner.setEnabled(enabled); mContentTypeSpinner.setEnabled(enabled); @@ -328,6 +379,8 @@ void updateDisplay(StreamConfiguration actualConfiguration) { mActualChannelCountView.setText(actualConfiguration.getChannelCount() + ""); mActualSampleRateView.setText(actualConfiguration.getSampleRate() + ""); mActualSessionIdView.setText("S#: " + actualConfiguration.getSessionId()); + value = actualConfiguration.getChannelMask(); + mActualChannelMaskView.setText(StreamConfiguration.convertChannelMaskToText(value)); boolean isMMap = actualConfiguration.isMMap(); mStreamInfoView.setText("burst = " + actualConfiguration.getFramesPerBurst() @@ -357,4 +410,12 @@ public void setFormatConversionAllowed(boolean allowed) { mFormatConversionBox.setChecked(allowed); } + private void onChannelCountSpinnerSelected() { + mIsChannelMaskLastSelected = false; + } + + private void onChannelMaskSpinnerSelected() { + mIsChannelMaskLastSelected = true; + } + } diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestOutputActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestOutputActivity.java index 94cc4cdf1..b4515ad5e 100644 --- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestOutputActivity.java +++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestOutputActivity.java @@ -31,7 +31,7 @@ */ public final class TestOutputActivity extends TestOutputActivityBase { - public static final int MAX_CHANNEL_BOXES = 8; + public static final int MAX_CHANNEL_BOXES = 16; private CheckBox[] mChannelBoxes; private Spinner mNativeApiSpinner; @@ -70,6 +70,14 @@ protected void onCreate(Bundle savedInstanceState) { mChannelBoxes[ic++] = (CheckBox) findViewById(R.id.channelBox5); mChannelBoxes[ic++] = (CheckBox) findViewById(R.id.channelBox6); mChannelBoxes[ic++] = (CheckBox) findViewById(R.id.channelBox7); + mChannelBoxes[ic++] = (CheckBox) findViewById(R.id.channelBox8); + mChannelBoxes[ic++] = (CheckBox) findViewById(R.id.channelBox9); + mChannelBoxes[ic++] = (CheckBox) findViewById(R.id.channelBox10); + mChannelBoxes[ic++] = (CheckBox) findViewById(R.id.channelBox11); + mChannelBoxes[ic++] = (CheckBox) findViewById(R.id.channelBox12); + mChannelBoxes[ic++] = (CheckBox) findViewById(R.id.channelBox13); + mChannelBoxes[ic++] = (CheckBox) findViewById(R.id.channelBox14); + mChannelBoxes[ic++] = (CheckBox) findViewById(R.id.channelBox15); configureChannelBoxes(0); mNativeApiSpinner = (Spinner) findViewById(R.id.spinnerOutputSignal); diff --git a/apps/OboeTester/app/src/main/res/layout/activity_test_output.xml b/apps/OboeTester/app/src/main/res/layout/activity_test_output.xml index 48d74f384..bc05814e0 100644 --- a/apps/OboeTester/app/src/main/res/layout/activity_test_output.xml +++ b/apps/OboeTester/app/src/main/res/layout/activity_test_output.xml @@ -12,74 +12,134 @@ - - - + + android:orientation="horizontal"> - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Best + ChannelMask: + diff --git a/include/oboe/AudioStreamBase.h b/include/oboe/AudioStreamBase.h index 148e31785..993ee58b1 100644 --- a/include/oboe/AudioStreamBase.h +++ b/include/oboe/AudioStreamBase.h @@ -178,6 +178,13 @@ class AudioStreamBase { return mSampleRateConversionQuality; } + /** + * @return the stream's channel mask. + */ + ChannelMask getChannelMask() const { + return mChannelMask; + } + protected: /** The callback which will be fired when new data is ready to be read/written. **/ AudioStreamDataCallback *mDataCallback = nullptr; @@ -197,6 +204,8 @@ class AudioStreamBase { int32_t mBufferCapacityInFrames = kUnspecified; /** Stream buffer size specified as a number of audio frames */ int32_t mBufferSizeInFrames = kUnspecified; + /** Stream channel mask. Only active on Android 32+ */ + ChannelMask mChannelMask = ChannelMask::Unspecified; /** Stream sharing mode */ SharingMode mSharingMode = SharingMode::Shared; diff --git a/include/oboe/AudioStreamBuilder.h b/include/oboe/AudioStreamBuilder.h index d0a97ce57..ac5a8199e 100644 --- a/include/oboe/AudioStreamBuilder.h +++ b/include/oboe/AudioStreamBuilder.h @@ -19,6 +19,7 @@ #include "oboe/Definitions.h" #include "oboe/AudioStreamBase.h" +#include "oboe/Utilities.h" #include "ResultWithValue.h" namespace oboe { @@ -42,12 +43,36 @@ class AudioStreamBuilder : public AudioStreamBase { * * Default is kUnspecified. If the value is unspecified then * the application should query for the actual value after the stream is opened. + * + * As the channel count here may be different from the corresponding channel count of + * provided channel mask used in setChannelMask(). The last called will be respected + * if this function and setChannelMask() are called. */ AudioStreamBuilder *setChannelCount(int channelCount) { mChannelCount = channelCount; + mChannelMask = ChannelMask::Unspecified; return this; } + /** + * Request a specific channel mask. + * + * Default is kUnspecified. If the value is unspecified then the application + * should query for the actual value after the stream is opened. + * + * As the corresponding channel count of provided channel mask here may be different + * from the channel count used in setChannelCount(). The last called will be respected + * if this function and setChannelCount() are called. + * + * As the setChannelMask API is available on Android 32+, this call will only take effects + * on Android 32+. + */ + AudioStreamBuilder *setChannelMask(ChannelMask channelMask) { + mChannelMask = channelMask; + mChannelCount = getChannelCountFromChannelMask(channelMask); + return this; + } + /** * Request the direction for a stream. The default is Direction::Output. * diff --git a/include/oboe/Definitions.h b/include/oboe/Definitions.h index c60d4c302..c24ea32cd 100644 --- a/include/oboe/Definitions.h +++ b/include/oboe/Definitions.h @@ -485,6 +485,163 @@ namespace oboe { Stereo = 2, }; + /** + * The channel mask of the audio stream. The underlying type is `uint32_t`. + * Use of this enum is convenient + */ + enum class ChannelMask : uint32_t { // aaudio_channel_mask_t + /** + * Audio channel mask is not specified. + */ + Unspecified = kUnspecified, + + /** + * Channel position masks. Use the combinations of the channel position masks + * defined below instead of using those values directly. + */ + FrontLeft = 1 << 0, + FrontRight = 1 << 1, + FrontCenter = 1 << 2, + LowFrequency = 1 << 3, + BackLeft = 1 << 4, + BackRight = 1 << 5, + FrontLeftOfCenter = 1 << 6, + FrontRightOfCenter = 1 << 7, + BackCenter = 1 << 8, + SideLeft = 1 << 9, + SideRight = 1 << 10, + TopCenter = 1 << 11, + TopFrontLeft = 1 << 12, + TopFrontCenter = 1 << 13, + TopFrontRight = 1 << 14, + TopBackLeft = 1 << 15, + TopBackCenter = 1 << 16, + TopBackRight = 1 << 17, + TopSideLeft = 1 << 18, + TopSideRight = 1 << 19, + BottomFrontLeft = 1 << 20, + BottomFrontCenter = 1 << 21, + BottomFrontRight = 1 << 22, + LowFrequency2 = 1 << 23, + FrontWideLeft = 1 << 24, + FrontWideRight = 1 << 25, + + Mono = FrontLeft, + + Stereo = FrontLeft | + FrontRight, + + CM2Point1 = FrontLeft | + FrontRight | + LowFrequency, + + Tri = FrontLeft | + FrontRight | + FrontCenter, + + TriBack = FrontLeft | + FrontRight | + BackCenter, + + CM3Point1 = FrontLeft | + FrontRight | + FrontCenter | + LowFrequency, + + CM2Point0Point2 = FrontLeft | + FrontRight | + TopSideLeft | + TopSideRight, + + CM2Point1Point2 = CM2Point0Point2 | + LowFrequency, + + CM3Point0Point2 = FrontLeft | + FrontRight | + FrontCenter | + TopSideLeft | + TopSideRight, + + CM3Point1Point2 = CM3Point0Point2 | + LowFrequency, + + Quad = FrontLeft | + FrontRight | + BackLeft | + BackRight, + + QuadSide = FrontLeft | + FrontRight | + SideLeft | + SideRight, + + Surround = FrontLeft | + FrontRight | + FrontCenter | + BackCenter, + + Penta = Quad | + FrontCenter, + + // aka 5Point1Back + CM5Point1 = FrontLeft | + FrontRight | + FrontCenter | + LowFrequency | + BackLeft | + BackRight, + + CM5Point1Side = FrontLeft | + FrontRight | + FrontCenter | + LowFrequency | + SideLeft | + SideRight, + + CM6Point1 = FrontLeft | + FrontRight | + FrontCenter | + LowFrequency | + BackLeft | + BackRight | + BackCenter, + + CM7Point1 = CM5Point1 | + SideLeft | + SideRight, + + CM5Point1Point2 = CM5Point1 | + TopSideLeft | + TopSideRight, + + CM5Point1Point4 = CM5Point1 | + TopFrontLeft | + TopFrontRight | + TopBackLeft | + TopBackRight, + + CM7Point1Point2 = CM7Point1 | + TopSideLeft | + TopSideRight, + + CM7Point1Point4 = CM7Point1 | + TopFrontLeft | + TopFrontRight | + TopBackLeft | + TopBackRight, + + CM9Point1Point4 = CM7Point1Point4 | + FrontWideLeft | + FrontWideRight, + + CM9Point1Point6 = CM9Point1Point4 | + TopSideLeft | + TopSideRight, + + FrontBack = FrontCenter | + BackCenter, + }; + /** * On API 16 to 26 OpenSL ES will be used. When using OpenSL ES the optimal values for sampleRate and * framesPerBurst are not known by the native code. diff --git a/include/oboe/Utilities.h b/include/oboe/Utilities.h index 2c270881c..a5e91709c 100644 --- a/include/oboe/Utilities.h +++ b/include/oboe/Utilities.h @@ -82,6 +82,8 @@ int getPropertyInteger(const char * name, int defaultValue); */ int getSdkVersion(); +int getChannelCountFromChannelMask(ChannelMask channelMask); + } // namespace oboe #endif //OBOE_UTILITIES_H diff --git a/src/aaudio/AAudioLoader.cpp b/src/aaudio/AAudioLoader.cpp index 8e43d7242..82595fd9e 100644 --- a/src/aaudio/AAudioLoader.cpp +++ b/src/aaudio/AAudioLoader.cpp @@ -88,6 +88,10 @@ int AAudioLoader::open() { builder_setAttributionTag = load_V_PBCPH("AAudioStreamBuilder_setAttributionTag"); } + if (getSdkVersion() >= __ANDROID_API_S_V2__) { + builder_setChannelMask = load_V_PBU("AAudioStreamBuilder_setChannelMask"); + } + builder_delete = load_I_PB("AAudioStreamBuilder_delete"); @@ -138,6 +142,10 @@ int AAudioLoader::open() { stream_getInputPreset = load_I_PS("AAudioStream_getInputPreset"); stream_getSessionId = load_I_PS("AAudioStream_getSessionId"); } + + if (getSdkVersion() >= __ANDROID_API_S_V2__) { + stream_getChannelMask = load_U_PS("AAudioStream_getChannelMask"); + } return 0; } @@ -249,10 +257,26 @@ AAudioLoader::signature_I_PSKPLPL AAudioLoader::load_I_PSKPLPL(const char *funct return reinterpret_cast(proc); } +AAudioLoader::signature_V_PBU AAudioLoader::load_V_PBU(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + +AAudioLoader::signature_U_PS AAudioLoader::load_U_PS(const char *functionName) { + void *proc = dlsym(mLibHandle, functionName); + AAudioLoader_check(proc, functionName); + return reinterpret_cast(proc); +} + // Ensure that all AAudio primitive data types are int32_t #define ASSERT_INT32(type) static_assert(std::is_same::value, \ #type" must be int32_t") +// Ensure that all AAudio primitive data types are uint32_t +#define ASSERT_UINT32(type) static_assert(std::is_same::value, \ +#type" must be uint32_t") + #define ERRMSG "Oboe constants must match AAudio constants." // These asserts help verify that the Oboe definitions match the equivalent AAudio definitions. @@ -361,6 +385,66 @@ AAudioLoader::signature_I_PSKPLPL AAudioLoader::load_I_PSKPLPL(const char *funct #endif // __NDK_MAJOR__ >= 17 +// The aaudio channel masks were added in NDK 24, +// which is the first version to support Android SC_V2 (API 32). +#if __NDK_MAJOR__ >= 24 + + ASSERT_UINT32(aaudio_channel_mask_t); + + static_assert((uint32_t)ChannelMask::FrontLeft == AAUDIO_CHANNEL_FRONT_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::FrontRight == AAUDIO_CHANNEL_FRONT_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::FrontCenter == AAUDIO_CHANNEL_FRONT_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::LowFrequency == AAUDIO_CHANNEL_LOW_FREQUENCY, ERRMSG); + static_assert((uint32_t)ChannelMask::BackLeft == AAUDIO_CHANNEL_BACK_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::BackRight == AAUDIO_CHANNEL_BACK_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::FrontLeftOfCenter == AAUDIO_CHANNEL_FRONT_LEFT_OF_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::FrontRightOfCenter == AAUDIO_CHANNEL_FRONT_RIGHT_OF_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::BackCenter == AAUDIO_CHANNEL_BACK_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::SideLeft == AAUDIO_CHANNEL_SIDE_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::SideRight == AAUDIO_CHANNEL_SIDE_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::TopCenter == AAUDIO_CHANNEL_TOP_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::TopFrontLeft == AAUDIO_CHANNEL_TOP_FRONT_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::TopFrontCenter == AAUDIO_CHANNEL_TOP_FRONT_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::TopFrontRight == AAUDIO_CHANNEL_TOP_FRONT_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::TopBackLeft == AAUDIO_CHANNEL_TOP_BACK_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::TopBackCenter == AAUDIO_CHANNEL_TOP_BACK_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::TopBackRight == AAUDIO_CHANNEL_TOP_BACK_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::TopSideLeft == AAUDIO_CHANNEL_TOP_SIDE_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::TopSideRight == AAUDIO_CHANNEL_TOP_SIDE_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::BottomFrontLeft == AAUDIO_CHANNEL_BOTTOM_FRONT_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::BottomFrontCenter == AAUDIO_CHANNEL_BOTTOM_FRONT_CENTER, ERRMSG); + static_assert((uint32_t)ChannelMask::BottomFrontRight == AAUDIO_CHANNEL_BOTTOM_FRONT_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::LowFrequency2 == AAUDIO_CHANNEL_LOW_FREQUENCY_2, ERRMSG); + static_assert((uint32_t)ChannelMask::FrontWideLeft == AAUDIO_CHANNEL_FRONT_WIDE_LEFT, ERRMSG); + static_assert((uint32_t)ChannelMask::FrontWideRight == AAUDIO_CHANNEL_FRONT_WIDE_RIGHT, ERRMSG); + static_assert((uint32_t)ChannelMask::Mono == AAUDIO_CHANNEL_MONO, ERRMSG); + static_assert((uint32_t)ChannelMask::Stereo == AAUDIO_CHANNEL_STEREO, ERRMSG); + static_assert((uint32_t)ChannelMask::CM2Point1 == AAUDIO_CHANNEL_2POINT1, ERRMSG); + static_assert((uint32_t)ChannelMask::Tri == AAUDIO_CHANNEL_TRI, ERRMSG); + static_assert((uint32_t)ChannelMask::TriBack == AAUDIO_CHANNEL_TRI_BACK, ERRMSG); + static_assert((uint32_t)ChannelMask::CM3Point1 == AAUDIO_CHANNEL_3POINT1, ERRMSG); + static_assert((uint32_t)ChannelMask::CM2Point0Point2 == AAUDIO_CHANNEL_2POINT0POINT2, ERRMSG); + static_assert((uint32_t)ChannelMask::CM2Point1Point2 == AAUDIO_CHANNEL_2POINT1POINT2, ERRMSG); + static_assert((uint32_t)ChannelMask::CM3Point0Point2 == AAUDIO_CHANNEL_3POINT0POINT2, ERRMSG); + static_assert((uint32_t)ChannelMask::CM3Point1Point2 == AAUDIO_CHANNEL_3POINT1POINT2, ERRMSG); + static_assert((uint32_t)ChannelMask::Quad == AAUDIO_CHANNEL_QUAD, ERRMSG); + static_assert((uint32_t)ChannelMask::QuadSide == AAUDIO_CHANNEL_QUAD_SIDE, ERRMSG); + static_assert((uint32_t)ChannelMask::Surround == AAUDIO_CHANNEL_SURROUND, ERRMSG); + static_assert((uint32_t)ChannelMask::Penta == AAUDIO_CHANNEL_PENTA, ERRMSG); + static_assert((uint32_t)ChannelMask::CM5Point1 == AAUDIO_CHANNEL_5POINT1, ERRMSG); + static_assert((uint32_t)ChannelMask::CM5Point1Side == AAUDIO_CHANNEL_5POINT1_SIDE, ERRMSG); + static_assert((uint32_t)ChannelMask::CM6Point1 == AAUDIO_CHANNEL_6POINT1, ERRMSG); + static_assert((uint32_t)ChannelMask::CM7Point1 == AAUDIO_CHANNEL_7POINT1, ERRMSG); + static_assert((uint32_t)ChannelMask::CM5Point1Point2 == AAUDIO_CHANNEL_5POINT1POINT2, ERRMSG); + static_assert((uint32_t)ChannelMask::CM5Point1Point4 == AAUDIO_CHANNEL_5POINT1POINT4, ERRMSG); + static_assert((uint32_t)ChannelMask::CM7Point1Point2 == AAUDIO_CHANNEL_7POINT1POINT2, ERRMSG); + static_assert((uint32_t)ChannelMask::CM7Point1Point4 == AAUDIO_CHANNEL_7POINT1POINT4, ERRMSG); + static_assert((uint32_t)ChannelMask::CM9Point1Point4 == AAUDIO_CHANNEL_9POINT1POINT4, ERRMSG); + static_assert((uint32_t)ChannelMask::CM9Point1Point6 == AAUDIO_CHANNEL_9POINT1POINT6, ERRMSG); + static_assert((uint32_t)ChannelMask::FrontBack == AAUDIO_CHANNEL_FRONT_BACK, ERRMSG); + +#endif + #endif // AAUDIO_AAUDIO_H } // namespace oboe diff --git a/src/aaudio/AAudioLoader.h b/src/aaudio/AAudioLoader.h index 8efb6d277..ea00a47f2 100644 --- a/src/aaudio/AAudioLoader.h +++ b/src/aaudio/AAudioLoader.h @@ -66,10 +66,19 @@ typedef int32_t aaudio_session_id_t; #define __NDK_MAJOR__ 0 #endif +#if __NDK_MAJOR__ < 24 +// Defined in SC_V2 +typedef uint32_t aaudio_channel_mask_t; +#endif + #ifndef __ANDROID_API_S__ #define __ANDROID_API_S__ 31 #endif +#ifndef __ANDROID_API_S_V2__ +#define __ANDROID_API_S_V2__ 32 +#endif + namespace oboe { /** @@ -90,6 +99,7 @@ class AAudioLoader { // P = Pointer to following data type // C = Const prefix // H = cHar + // U = uint32_t typedef int32_t (*signature_I_PPB)(AAudioStreamBuilder **builder); typedef const char * (*signature_CPH_I)(int32_t); @@ -101,6 +111,9 @@ class AAudioLoader { // AAudioStreamBuilder_setSampleRate() typedef void (*signature_V_PBI)(AAudioStreamBuilder *, int32_t); + // AAudioStreamBuilder_setChannelMask() + typedef void (*signature_V_PBU)(AAudioStreamBuilder *, uint32_t); + typedef void (*signature_V_PBCPH)(AAudioStreamBuilder *, const char *); typedef int32_t (*signature_I_PS)(AAudioStream *); // AAudioStream_getSampleRate() @@ -130,6 +143,8 @@ class AAudioLoader { typedef bool (*signature_B_PS)(AAudioStream *); + typedef uint32_t (*signature_U_PS)(AAudioStream *); + static AAudioLoader* getInstance(); // singleton /** @@ -159,6 +174,7 @@ class AAudioLoader { signature_V_PBI builder_setPerformanceMode = nullptr; signature_V_PBI builder_setSampleRate = nullptr; signature_V_PBI builder_setSharingMode = nullptr; + signature_V_PBU builder_setChannelMask = nullptr; signature_V_PBI builder_setUsage = nullptr; signature_V_PBI builder_setContentType = nullptr; @@ -212,6 +228,8 @@ class AAudioLoader { signature_I_PS stream_getInputPreset = nullptr; signature_I_PS stream_getSessionId = nullptr; + signature_U_PS stream_getChannelMask = nullptr; + private: AAudioLoader() {} ~AAudioLoader(); @@ -234,6 +252,8 @@ class AAudioLoader { signature_I_PSCPVIL load_I_PSCPVIL(const char *name); signature_I_PSTPTL load_I_PSTPTL(const char *name); signature_I_PSKPLPL load_I_PSKPLPL(const char *name); + signature_V_PBU load_V_PBU(const char *name); + signature_U_PS load_U_PS(const char *name); void *mLibHandle = nullptr; }; diff --git a/src/aaudio/AudioStreamAAudio.cpp b/src/aaudio/AudioStreamAAudio.cpp index 8e61d7dae..200d05b02 100644 --- a/src/aaudio/AudioStreamAAudio.cpp +++ b/src/aaudio/AudioStreamAAudio.cpp @@ -205,7 +205,18 @@ Result AudioStreamAAudio::open() { } mLibLoader->builder_setBufferCapacityInFrames(aaudioBuilder, capacity); - mLibLoader->builder_setChannelCount(aaudioBuilder, mChannelCount); + // Channel mask was added in SC_V2. Given the corresponding channel count of selected channel + // mask may be different from selected channel count, the last set value will be respected. + // If channel count is set after channel mask, the previously set channel mask will be cleared. + // If channel mask is set after channel count, the channel count will be automatically + // calculated from selected channel mask. In that case, only set channel mask when the API + // is available and the channel mask is specified. + if (mLibLoader->builder_setChannelMask != nullptr && mChannelMask != ChannelMask::Unspecified) { + mLibLoader->builder_setChannelMask(aaudioBuilder, + static_cast(mChannelMask)); + } else { + mLibLoader->builder_setChannelCount(aaudioBuilder, mChannelCount); + } mLibLoader->builder_setDeviceId(aaudioBuilder, mDeviceId); mLibLoader->builder_setDirection(aaudioBuilder, static_cast(mDirection)); mLibLoader->builder_setFormat(aaudioBuilder, static_cast(mFormat)); @@ -309,6 +320,10 @@ Result AudioStreamAAudio::open() { mSessionId = SessionId::None; } + if (mLibLoader->stream_getChannelMask != nullptr) { + mChannelMask = static_cast(mLibLoader->stream_getChannelMask(mAAudioStream)); + } + LOGD("AudioStreamAAudio.open() format=%d, sampleRate=%d, capacity = %d", static_cast(mFormat), static_cast(mSampleRate), static_cast(mBufferCapacityInFrames)); diff --git a/src/common/Utilities.cpp b/src/common/Utilities.cpp index 177c544b3..22296e685 100644 --- a/src/common/Utilities.cpp +++ b/src/common/Utilities.cpp @@ -310,4 +310,8 @@ int getSdkVersion() { return sCachedSdkVersion; } +int getChannelCountFromChannelMask(ChannelMask channelMask) { + return __builtin_popcount(static_cast(channelMask)); +} + }// namespace oboe