Merge "Add APIs for setting preferred mixer attributes."
This commit is contained in:
committed by
Android (Google) Code Review
commit
24a4e3fd42
@@ -20549,10 +20549,12 @@ package android.media {
|
||||
method public int abandonAudioFocusRequest(@NonNull android.media.AudioFocusRequest);
|
||||
method public void addOnCommunicationDeviceChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnCommunicationDeviceChangedListener);
|
||||
method public void addOnModeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnModeChangedListener);
|
||||
method public void addOnPreferredMixerAttributesChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredMixerAttributesChangedListener);
|
||||
method public void adjustStreamVolume(int, int, int);
|
||||
method public void adjustSuggestedStreamVolume(int, int, int);
|
||||
method public void adjustVolume(int, int);
|
||||
method public void clearCommunicationDevice();
|
||||
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) public boolean clearPreferredMixerAttributes(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioDeviceInfo);
|
||||
method public void dispatchMediaKeyEvent(android.view.KeyEvent);
|
||||
method public int generateAudioSessionId();
|
||||
method @NonNull public java.util.List<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations();
|
||||
@@ -20570,6 +20572,7 @@ package android.media {
|
||||
method public int getMode();
|
||||
method public String getParameters(String);
|
||||
method @Deprecated public static int getPlaybackOffloadSupport(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
|
||||
method @Nullable public android.media.AudioMixerAttributes getPreferredMixerAttributes(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioDeviceInfo);
|
||||
method public String getProperty(String);
|
||||
method public int getRingerMode();
|
||||
method @Deprecated public int getRouting(int);
|
||||
@@ -20578,6 +20581,7 @@ package android.media {
|
||||
method public int getStreamMinVolume(int);
|
||||
method public int getStreamVolume(int);
|
||||
method public float getStreamVolumeDb(int, int, int);
|
||||
method @NonNull public java.util.List<android.media.AudioMixerAttributes> getSupportedMixerAttributes(@NonNull android.media.AudioDeviceInfo);
|
||||
method @Deprecated public int getVibrateSetting(int);
|
||||
method @Deprecated public boolean isBluetoothA2dpOn();
|
||||
method public boolean isBluetoothScoAvailableOffCall();
|
||||
@@ -20605,6 +20609,7 @@ package android.media {
|
||||
method @Deprecated public boolean registerRemoteController(android.media.RemoteController);
|
||||
method public void removeOnCommunicationDeviceChangedListener(@NonNull android.media.AudioManager.OnCommunicationDeviceChangedListener);
|
||||
method public void removeOnModeChangedListener(@NonNull android.media.AudioManager.OnModeChangedListener);
|
||||
method public void removeOnPreferredMixerAttributesChangedListener(@NonNull android.media.AudioManager.OnPreferredMixerAttributesChangedListener);
|
||||
method @Deprecated public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int);
|
||||
method public int requestAudioFocus(@NonNull android.media.AudioFocusRequest);
|
||||
method public void setAllowedCapturePolicy(int);
|
||||
@@ -20615,6 +20620,7 @@ package android.media {
|
||||
method public void setMicrophoneMute(boolean);
|
||||
method public void setMode(int);
|
||||
method public void setParameters(String);
|
||||
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) public boolean setPreferredMixerAttributes(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioDeviceInfo, @NonNull android.media.AudioMixerAttributes);
|
||||
method public void setRingerMode(int);
|
||||
method @Deprecated public void setRouting(int, int, int);
|
||||
method @Deprecated public void setSpeakerphoneOn(boolean);
|
||||
@@ -20770,6 +20776,10 @@ package android.media {
|
||||
method public void onModeChanged(int);
|
||||
}
|
||||
|
||||
public static interface AudioManager.OnPreferredMixerAttributesChangedListener {
|
||||
method public void onPreferredMixerAttributesChanged(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioDeviceInfo, @Nullable android.media.AudioMixerAttributes);
|
||||
}
|
||||
|
||||
public final class AudioMetadata {
|
||||
method @NonNull public static android.media.AudioMetadataMap createMap();
|
||||
}
|
||||
@@ -20805,6 +20815,21 @@ package android.media {
|
||||
method @IntRange(from=0) public int size();
|
||||
}
|
||||
|
||||
public final class AudioMixerAttributes implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method @NonNull public android.media.AudioFormat getFormat();
|
||||
method public int getMixerBehavior();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioMixerAttributes> CREATOR;
|
||||
field public static final int MIXER_BEHAVIOR_DEFAULT = 0; // 0x0
|
||||
}
|
||||
|
||||
public static final class AudioMixerAttributes.Builder {
|
||||
ctor public AudioMixerAttributes.Builder(@NonNull android.media.AudioFormat);
|
||||
method @NonNull public android.media.AudioMixerAttributes build();
|
||||
method @NonNull public android.media.AudioMixerAttributes.Builder setMixerBehavior(int);
|
||||
}
|
||||
|
||||
public final class AudioPlaybackCaptureConfiguration {
|
||||
method @NonNull public int[] getExcludeUids();
|
||||
method @NonNull public int[] getExcludeUsages();
|
||||
|
||||
46
core/jni/android_media_AudioMixerAttributes.h
Normal file
46
core/jni/android_media_AudioMixerAttributes.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_MEDIA_AUDIOMIXERATTRIBUTES_H
|
||||
#define ANDROID_MEDIA_AUDIOMIXERATTRIBUTES_H
|
||||
|
||||
#include <system/audio.h>
|
||||
|
||||
// Keep sync with AudioMixerAttributes.java
|
||||
#define MIXER_BEHAVIOR_DEFAULT 0
|
||||
// Invalid value is not added in JAVA API, but keep sync with native value
|
||||
#define MIXER_BEHAVIOR_INVALID -1
|
||||
|
||||
static inline audio_mixer_behavior_t audioMixerBehaviorToNative(int mixerBehavior) {
|
||||
switch (mixerBehavior) {
|
||||
case MIXER_BEHAVIOR_DEFAULT:
|
||||
return AUDIO_MIXER_BEHAVIOR_DEFAULT;
|
||||
default:
|
||||
return AUDIO_MIXER_BEHAVIOR_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static inline jint audioMixerBehaviorFromNative(audio_mixer_behavior_t mixerBehavior) {
|
||||
switch (mixerBehavior) {
|
||||
case AUDIO_MIXER_BEHAVIOR_DEFAULT:
|
||||
return MIXER_BEHAVIOR_DEFAULT;
|
||||
case AUDIO_MIXER_BEHAVIOR_INVALID:
|
||||
default:
|
||||
return MIXER_BEHAVIOR_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <android/media/AudioVibratorInfo.h>
|
||||
#include <android/media/INativeSpatializerCallback.h>
|
||||
#include <android/media/ISpatializer.h>
|
||||
#include <android/media/audio/common/AudioConfigBase.h>
|
||||
#include <android_os_Parcel.h>
|
||||
#include <audiomanager/AudioManager.h>
|
||||
#include <jni.h>
|
||||
@@ -34,6 +35,7 @@
|
||||
#include <system/audio_policy.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
@@ -43,6 +45,7 @@
|
||||
#include "android_media_AudioEffectDescriptor.h"
|
||||
#include "android_media_AudioErrors.h"
|
||||
#include "android_media_AudioFormat.h"
|
||||
#include "android_media_AudioMixerAttributes.h"
|
||||
#include "android_media_AudioProfile.h"
|
||||
#include "android_media_MicrophoneInfo.h"
|
||||
#include "android_util_Binder.h"
|
||||
@@ -51,6 +54,7 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
using namespace android;
|
||||
using media::audio::common::AudioConfigBase;
|
||||
|
||||
static const char* const kClassPathName = "android/media/AudioSystem";
|
||||
|
||||
@@ -145,10 +149,12 @@ static struct {
|
||||
} gAudioMixFields;
|
||||
|
||||
static jclass gAudioFormatClass;
|
||||
static jmethodID gAudioFormatCstor;
|
||||
static struct {
|
||||
jfieldID mEncoding;
|
||||
jfieldID mSampleRate;
|
||||
jfieldID mChannelMask;
|
||||
jfieldID mChannelIndexMask;
|
||||
// other fields unused by JNI
|
||||
} gAudioFormatFields;
|
||||
|
||||
@@ -211,6 +217,7 @@ static struct {
|
||||
jfieldID mChannelMasks;
|
||||
jfieldID mChannelIndexMasks;
|
||||
jfieldID mEncapsulationType;
|
||||
jfieldID mMixerBehaviors;
|
||||
} gAudioProfileFields;
|
||||
|
||||
jclass gVibratorClass;
|
||||
@@ -221,6 +228,13 @@ static struct {
|
||||
jmethodID getMaxAmplitude;
|
||||
} gVibratorMethods;
|
||||
|
||||
jclass gAudioMixerAttributesClass;
|
||||
jmethodID gAudioMixerAttributesCstor;
|
||||
static struct {
|
||||
jfieldID mFormat;
|
||||
jfieldID mMixerBehavior;
|
||||
} gAudioMixerAttributesField;
|
||||
|
||||
static Mutex gLock;
|
||||
|
||||
enum AudioError {
|
||||
@@ -237,6 +251,12 @@ enum {
|
||||
|
||||
#define MAX_PORT_GENERATION_SYNC_ATTEMPTS 5
|
||||
|
||||
// Keep sync with AudioFormat.java
|
||||
#define AUDIO_FORMAT_HAS_PROPERTY_ENCODING 0x1
|
||||
#define AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE 0x2
|
||||
#define AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK 0x4
|
||||
#define AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK 0x8
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ref-counted object for audio port callbacks
|
||||
class JNIAudioPortCallback: public AudioSystem::AudioPortCallback
|
||||
@@ -2105,6 +2125,80 @@ void javaAudioFormatToNativeAudioConfig(JNIEnv *env, audio_config_t *nConfig,
|
||||
}
|
||||
}
|
||||
|
||||
void javaAudioFormatToNativeAudioConfigBase(JNIEnv *env, const jobject jFormat,
|
||||
audio_config_base_t *nConfigBase, bool isInput) {
|
||||
*nConfigBase = AUDIO_CONFIG_BASE_INITIALIZER;
|
||||
nConfigBase->format =
|
||||
audioFormatToNative(env->GetIntField(jFormat, gAudioFormatFields.mEncoding));
|
||||
nConfigBase->sample_rate = env->GetIntField(jFormat, gAudioFormatFields.mSampleRate);
|
||||
jint jChannelMask = env->GetIntField(jFormat, gAudioFormatFields.mChannelMask);
|
||||
jint jChannelIndexMask = env->GetIntField(jFormat, gAudioFormatFields.mChannelIndexMask);
|
||||
nConfigBase->channel_mask = jChannelIndexMask != 0
|
||||
? audio_channel_mask_from_representation_and_bits(AUDIO_CHANNEL_REPRESENTATION_INDEX,
|
||||
jChannelIndexMask)
|
||||
: isInput ? inChannelMaskToNative(jChannelMask)
|
||||
: outChannelMaskToNative(jChannelMask);
|
||||
}
|
||||
|
||||
jobject nativeAudioConfigBaseToJavaAudioFormat(JNIEnv *env, const audio_config_base_t *nConfigBase,
|
||||
bool isInput) {
|
||||
if (nConfigBase == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
int propertyMask = AUDIO_FORMAT_HAS_PROPERTY_ENCODING | AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE;
|
||||
int channelMask = 0;
|
||||
int channelIndexMask = 0;
|
||||
switch (audio_channel_mask_get_representation(nConfigBase->channel_mask)) {
|
||||
case AUDIO_CHANNEL_REPRESENTATION_POSITION:
|
||||
channelMask = isInput ? inChannelMaskFromNative(nConfigBase->channel_mask)
|
||||
: outChannelMaskFromNative(nConfigBase->channel_mask);
|
||||
propertyMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK;
|
||||
break;
|
||||
case AUDIO_CHANNEL_REPRESENTATION_INDEX:
|
||||
channelIndexMask = audio_channel_mask_get_bits(nConfigBase->channel_mask);
|
||||
propertyMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK;
|
||||
break;
|
||||
default:
|
||||
// This must not happen
|
||||
break;
|
||||
}
|
||||
return env->NewObject(gAudioFormatClass, gAudioFormatCstor, propertyMask,
|
||||
audioFormatFromNative(nConfigBase->format), nConfigBase->sample_rate,
|
||||
channelMask, channelIndexMask);
|
||||
}
|
||||
|
||||
jint convertAudioMixerAttributesToNative(JNIEnv *env, const jobject jAudioMixerAttributes,
|
||||
audio_mixer_attributes_t *nMixerAttributes) {
|
||||
ScopedLocalRef<jobject> jFormat(env,
|
||||
env->GetObjectField(jAudioMixerAttributes,
|
||||
gAudioMixerAttributesField.mFormat));
|
||||
javaAudioFormatToNativeAudioConfigBase(env, jFormat.get(), &nMixerAttributes->config,
|
||||
false /*isInput*/);
|
||||
nMixerAttributes->mixer_behavior = audioMixerBehaviorToNative(
|
||||
env->GetIntField(jAudioMixerAttributes, gAudioMixerAttributesField.mMixerBehavior));
|
||||
if (nMixerAttributes->mixer_behavior == AUDIO_MIXER_BEHAVIOR_INVALID) {
|
||||
return (jint)AUDIO_JAVA_BAD_VALUE;
|
||||
}
|
||||
return (jint)AUDIO_JAVA_SUCCESS;
|
||||
}
|
||||
|
||||
jobject convertAudioMixerAttributesFromNative(JNIEnv *env,
|
||||
const audio_mixer_attributes_t *nMixerAttributes) {
|
||||
if (nMixerAttributes == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
jint mixerBehavior = audioMixerBehaviorFromNative(nMixerAttributes->mixer_behavior);
|
||||
if (mixerBehavior == MIXER_BEHAVIOR_INVALID) {
|
||||
return nullptr;
|
||||
}
|
||||
ScopedLocalRef<jobject>
|
||||
jFormat(env,
|
||||
nativeAudioConfigBaseToJavaAudioFormat(env, &nMixerAttributes->config,
|
||||
false /*isInput*/));
|
||||
return env->NewObject(gAudioMixerAttributesClass, gAudioMixerAttributesCstor, jFormat.get(),
|
||||
mixerBehavior);
|
||||
}
|
||||
|
||||
static jint convertAudioMixToNative(JNIEnv *env,
|
||||
AudioMix *nAudioMix,
|
||||
const jobject jAudioMix)
|
||||
@@ -2966,6 +3060,142 @@ static jint android_media_AudioSystem_getDirectProfilesForAttributes(JNIEnv *env
|
||||
return jStatus;
|
||||
}
|
||||
|
||||
static jint android_media_AudioSystem_getSupportedMixerAttributes(JNIEnv *env, jobject thiz,
|
||||
jint jDeviceId,
|
||||
jobject jAudioMixerAttributes) {
|
||||
ALOGV("%s", __func__);
|
||||
if (jAudioMixerAttributes == NULL) {
|
||||
ALOGE("getSupportedMixerAttributes NULL AudioMixerAttributes list");
|
||||
return (jint)AUDIO_JAVA_BAD_VALUE;
|
||||
}
|
||||
if (!env->IsInstanceOf(jAudioMixerAttributes, gListClass)) {
|
||||
ALOGE("getSupportedMixerAttributes not a list");
|
||||
return (jint)AUDIO_JAVA_BAD_VALUE;
|
||||
}
|
||||
|
||||
std::vector<audio_mixer_attributes_t> nMixerAttributes;
|
||||
status_t status = AudioSystem::getSupportedMixerAttributes((audio_port_handle_t)jDeviceId,
|
||||
&nMixerAttributes);
|
||||
if (status != NO_ERROR) {
|
||||
return nativeToJavaStatus(status);
|
||||
}
|
||||
for (const auto &mixerAttr : nMixerAttributes) {
|
||||
ScopedLocalRef<jobject> jMixerAttributes(env,
|
||||
convertAudioMixerAttributesFromNative(env,
|
||||
&mixerAttr));
|
||||
if (jMixerAttributes.get() == nullptr) {
|
||||
return (jint)AUDIO_JAVA_ERROR;
|
||||
}
|
||||
|
||||
env->CallBooleanMethod(jAudioMixerAttributes, gListMethods.add, jMixerAttributes.get());
|
||||
}
|
||||
|
||||
return (jint)AUDIO_JAVA_SUCCESS;
|
||||
}
|
||||
|
||||
static jint android_media_AudioSystem_setPreferredMixerAttributes(JNIEnv *env, jobject thiz,
|
||||
jobject jAudioAttributes,
|
||||
jint portId, jint uid,
|
||||
jobject jAudioMixerAttributes) {
|
||||
ALOGV("%s", __func__);
|
||||
|
||||
if (jAudioAttributes == nullptr) {
|
||||
ALOGE("jAudioAttributes is NULL");
|
||||
return (jint)AUDIO_JAVA_BAD_VALUE;
|
||||
}
|
||||
if (jAudioMixerAttributes == nullptr) {
|
||||
ALOGE("jAudioMixerAttributes is NULL");
|
||||
return (jint)AUDIO_JAVA_BAD_VALUE;
|
||||
}
|
||||
|
||||
JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
|
||||
jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get());
|
||||
if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
|
||||
return jStatus;
|
||||
}
|
||||
|
||||
audio_mixer_attributes_t mixerAttributes = AUDIO_MIXER_ATTRIBUTES_INITIALIZER;
|
||||
jStatus = convertAudioMixerAttributesToNative(env, jAudioMixerAttributes, &mixerAttributes);
|
||||
if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
|
||||
return jStatus;
|
||||
}
|
||||
|
||||
status_t status =
|
||||
AudioSystem::setPreferredMixerAttributes(paa.get(), (audio_port_handle_t)portId,
|
||||
(uid_t)uid, &mixerAttributes);
|
||||
return nativeToJavaStatus(status);
|
||||
}
|
||||
|
||||
static jint android_media_AudioSystem_getPreferredMixerAttributes(JNIEnv *env, jobject thiz,
|
||||
jobject jAudioAttributes,
|
||||
jint portId,
|
||||
jobject jAudioMixerAttributes) {
|
||||
ALOGV("%s", __func__);
|
||||
|
||||
if (jAudioAttributes == nullptr) {
|
||||
ALOGE("getPreferredMixerAttributes jAudioAttributes is NULL");
|
||||
return (jint)AUDIO_JAVA_BAD_VALUE;
|
||||
}
|
||||
if (jAudioMixerAttributes == NULL) {
|
||||
ALOGE("getPreferredMixerAttributes NULL AudioMixerAttributes list");
|
||||
return (jint)AUDIO_JAVA_BAD_VALUE;
|
||||
}
|
||||
if (!env->IsInstanceOf(jAudioMixerAttributes, gListClass)) {
|
||||
ALOGE("getPreferredMixerAttributes not a list");
|
||||
return (jint)AUDIO_JAVA_BAD_VALUE;
|
||||
}
|
||||
|
||||
JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
|
||||
jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get());
|
||||
if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
|
||||
return jStatus;
|
||||
}
|
||||
|
||||
std::optional<audio_mixer_attributes_t> nMixerAttributes;
|
||||
status_t status =
|
||||
AudioSystem::getPreferredMixerAttributes(paa.get(), (audio_port_handle_t)portId,
|
||||
&nMixerAttributes);
|
||||
if (status != NO_ERROR) {
|
||||
return nativeToJavaStatus(status);
|
||||
}
|
||||
|
||||
ScopedLocalRef<jobject>
|
||||
jMixerAttributes(env,
|
||||
convertAudioMixerAttributesFromNative(env,
|
||||
nMixerAttributes.has_value()
|
||||
? &nMixerAttributes
|
||||
.value()
|
||||
: nullptr));
|
||||
if (jMixerAttributes.get() == nullptr) {
|
||||
return (jint)AUDIO_JAVA_ERROR;
|
||||
}
|
||||
|
||||
env->CallBooleanMethod(jAudioMixerAttributes, gListMethods.add, jMixerAttributes.get());
|
||||
return AUDIO_JAVA_SUCCESS;
|
||||
}
|
||||
|
||||
static jint android_media_AudioSystem_clearPreferredMixerAttributes(JNIEnv *env, jobject thiz,
|
||||
jobject jAudioAttributes,
|
||||
jint portId, jint uid) {
|
||||
ALOGV("%s", __func__);
|
||||
|
||||
if (jAudioAttributes == nullptr) {
|
||||
ALOGE("jAudioAttributes is NULL");
|
||||
return (jint)AUDIO_JAVA_BAD_VALUE;
|
||||
}
|
||||
|
||||
JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
|
||||
jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get());
|
||||
if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
|
||||
return jStatus;
|
||||
}
|
||||
|
||||
status_t status =
|
||||
AudioSystem::clearPreferredMixerAttributes(paa.get(), (audio_port_handle_t)portId,
|
||||
(uid_t)uid);
|
||||
return nativeToJavaStatus(status);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
static const JNINativeMethod gMethods[] =
|
||||
@@ -3120,7 +3350,16 @@ static const JNINativeMethod gMethods[] =
|
||||
(void *)android_media_AudioSystem_getDirectPlaybackSupport},
|
||||
{"getDirectProfilesForAttributes",
|
||||
"(Landroid/media/AudioAttributes;Ljava/util/ArrayList;)I",
|
||||
(void *)android_media_AudioSystem_getDirectProfilesForAttributes}};
|
||||
(void *)android_media_AudioSystem_getDirectProfilesForAttributes},
|
||||
{"getSupportedMixerAttributes", "(ILjava/util/List;)I",
|
||||
(void *)android_media_AudioSystem_getSupportedMixerAttributes},
|
||||
{"setPreferredMixerAttributes",
|
||||
"(Landroid/media/AudioAttributes;IILandroid/media/AudioMixerAttributes;)I",
|
||||
(void *)android_media_AudioSystem_setPreferredMixerAttributes},
|
||||
{"getPreferredMixerAttributes", "(Landroid/media/AudioAttributes;ILjava/util/List;)I",
|
||||
(void *)android_media_AudioSystem_getPreferredMixerAttributes},
|
||||
{"clearPreferredMixerAttributes", "(Landroid/media/AudioAttributes;II)I",
|
||||
(void *)android_media_AudioSystem_clearPreferredMixerAttributes}};
|
||||
|
||||
static const JNINativeMethod gEventHandlerMethods[] = {
|
||||
{"native_setup",
|
||||
@@ -3283,9 +3522,12 @@ int register_android_media_AudioSystem(JNIEnv *env)
|
||||
|
||||
jclass audioFormatClass = FindClassOrDie(env, "android/media/AudioFormat");
|
||||
gAudioFormatClass = MakeGlobalRefOrDie(env, audioFormatClass);
|
||||
gAudioFormatCstor = GetMethodIDOrDie(env, audioFormatClass, "<init>", "(IIIII)V");
|
||||
gAudioFormatFields.mEncoding = GetFieldIDOrDie(env, audioFormatClass, "mEncoding", "I");
|
||||
gAudioFormatFields.mSampleRate = GetFieldIDOrDie(env, audioFormatClass, "mSampleRate", "I");
|
||||
gAudioFormatFields.mChannelMask = GetFieldIDOrDie(env, audioFormatClass, "mChannelMask", "I");
|
||||
gAudioFormatFields.mChannelIndexMask =
|
||||
GetFieldIDOrDie(env, audioFormatClass, "mChannelIndexMask", "I");
|
||||
|
||||
jclass audioMixingRuleClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMixingRule");
|
||||
gAudioMixingRuleClass = MakeGlobalRefOrDie(env, audioMixingRuleClass);
|
||||
@@ -3359,6 +3601,15 @@ int register_android_media_AudioSystem(JNIEnv *env)
|
||||
gVibratorMethods.getMaxAmplitude =
|
||||
GetMethodIDOrDie(env, vibratorClass, "getHapticChannelMaximumAmplitude", "()F");
|
||||
|
||||
jclass audioMixerAttributesClass = FindClassOrDie(env, "android/media/AudioMixerAttributes");
|
||||
gAudioMixerAttributesClass = MakeGlobalRefOrDie(env, audioMixerAttributesClass);
|
||||
gAudioMixerAttributesCstor = GetMethodIDOrDie(env, audioMixerAttributesClass, "<init>",
|
||||
"(Landroid/media/AudioFormat;I)V");
|
||||
gAudioMixerAttributesField.mFormat = GetFieldIDOrDie(env, audioMixerAttributesClass, "mFormat",
|
||||
"Landroid/media/AudioFormat;");
|
||||
gAudioMixerAttributesField.mMixerBehavior =
|
||||
GetFieldIDOrDie(env, audioMixerAttributesClass, "mMixerBehavior", "I");
|
||||
|
||||
AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
|
||||
|
||||
RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
|
||||
|
||||
@@ -8518,6 +8518,221 @@ public class AudioManager {
|
||||
}
|
||||
}
|
||||
|
||||
//====================================================================
|
||||
// Preferred mixer attributes
|
||||
|
||||
/**
|
||||
* Returns the {@link AudioMixerAttributes} that can be used to set as preferred mixe
|
||||
* attributes via {@link #setPreferredMixerAttributes(
|
||||
* AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)}.
|
||||
* <p>Note that only USB devices are guaranteed to expose configurable mixer attributes, the
|
||||
* returned list may be empty when devices do not allow dynamic configuration.
|
||||
*
|
||||
* @param device the device to query
|
||||
* @return a list of {@link AudioMixerAttributes} that can be used as preferred mixer attributes
|
||||
* for the given device.
|
||||
* @see #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)
|
||||
*/
|
||||
@NonNull
|
||||
public List<AudioMixerAttributes> getSupportedMixerAttributes(@NonNull AudioDeviceInfo device) {
|
||||
Objects.requireNonNull(device);
|
||||
List<AudioMixerAttributes> mixerAttrs = new ArrayList<>();
|
||||
return (AudioSystem.getSupportedMixerAttributes(device.getId(), mixerAttrs)
|
||||
== AudioSystem.SUCCESS) ? mixerAttrs : new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the mixer attributes for a particular {@link AudioAttributes} over a given
|
||||
* {@link AudioDeviceInfo}.
|
||||
* <p>When constructing an {@link AudioMixerAttributes} for setting preferred mixer attributes,
|
||||
* the mixer format must be constructed from an {@link AudioProfile} that can be used to set
|
||||
* preferred mixer attributes.
|
||||
* <p>The ownership of preferred mixer attributes is recognized by uid. When a playback from the
|
||||
* same uid is routed to the given audio device when calling this API, the output mixer/stream
|
||||
* will be configured with the values previously set via this API.
|
||||
* <p>Use {@link #clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)}
|
||||
* to cancel setting mixer attributes for this {@link AudioAttributes}.
|
||||
*
|
||||
* @param attributes the {@link AudioAttributes} whose mixer attributes should be set.
|
||||
* Currently, only {@link AudioAttributes#USAGE_MEDIA} is supported. When
|
||||
* playing audio targeted at the given device, use the same attributes for
|
||||
* playback.
|
||||
* @param device the device to be routed. Currently, only USB device will be allowed.
|
||||
* @param mixerAttributes the preferred mixer attributes. When playing audio targeted at the
|
||||
* given device, use the same {@link AudioFormat} for both playback
|
||||
* and the mixer attributes.
|
||||
* @return true only if the preferred mixer attributes are set successfully.
|
||||
* @see #getPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)
|
||||
* @see #clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS)
|
||||
public boolean setPreferredMixerAttributes(@NonNull AudioAttributes attributes,
|
||||
@NonNull AudioDeviceInfo device,
|
||||
@NonNull AudioMixerAttributes mixerAttributes) {
|
||||
Objects.requireNonNull(attributes);
|
||||
Objects.requireNonNull(device);
|
||||
Objects.requireNonNull(mixerAttributes);
|
||||
try {
|
||||
final int status = getService().setPreferredMixerAttributes(
|
||||
attributes, device.getId(), mixerAttributes);
|
||||
return status == AudioSystem.SUCCESS;
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current preferred mixer attributes that is set via
|
||||
* {@link #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)}
|
||||
*
|
||||
* @param attributes the {@link AudioAttributes} whose mixer attributes should be set.
|
||||
* @param device the expected routing device
|
||||
* @return the preferred mixer attributes, which will be null when no preferred mixer attributes
|
||||
* have been set, or when they have been cleared.
|
||||
* @see #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)
|
||||
* @see #clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)
|
||||
*/
|
||||
@Nullable
|
||||
public AudioMixerAttributes getPreferredMixerAttributes(
|
||||
@NonNull AudioAttributes attributes,
|
||||
@NonNull AudioDeviceInfo device) {
|
||||
Objects.requireNonNull(attributes);
|
||||
Objects.requireNonNull(device);
|
||||
List<AudioMixerAttributes> mixerAttrList = new ArrayList<>();
|
||||
int ret = AudioSystem.getPreferredMixerAttributes(
|
||||
attributes, device.getId(), mixerAttrList);
|
||||
if (ret == AudioSystem.SUCCESS) {
|
||||
return mixerAttrList.isEmpty() ? null : mixerAttrList.get(0);
|
||||
} else {
|
||||
Log.e(TAG, "Failed calling getPreferredMixerAttributes, ret=" + ret);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the current preferred mixer attributes that were previously set via
|
||||
* {@link #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)}
|
||||
*
|
||||
* @param attributes the {@link AudioAttributes} whose mixer attributes should be cleared.
|
||||
* @param device the expected routing device
|
||||
* @return true only if the preferred mixer attributes are removed successfully.
|
||||
* @see #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)
|
||||
* @see #getPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS)
|
||||
public boolean clearPreferredMixerAttributes(
|
||||
@NonNull AudioAttributes attributes,
|
||||
@NonNull AudioDeviceInfo device) {
|
||||
Objects.requireNonNull(attributes);
|
||||
Objects.requireNonNull(device);
|
||||
try {
|
||||
final int status = getService().clearPreferredMixerAttributes(
|
||||
attributes, device.getId());
|
||||
return status == AudioSystem.SUCCESS;
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to be notified of changes in the preferred mixer attributes.
|
||||
* <p>Note that this listener will only be invoked whenever
|
||||
* {@link #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)}
|
||||
* or {@link #clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)} or device
|
||||
* disconnection causes a change in preferred mixer attributes.
|
||||
* @see #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)
|
||||
* @see #clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)
|
||||
*/
|
||||
public interface OnPreferredMixerAttributesChangedListener {
|
||||
/**
|
||||
* Called on the listener to indicate that the preferred mixer attributes for the audio
|
||||
* attributes over the given device has changed.
|
||||
*
|
||||
* @param attributes the audio attributes for playback
|
||||
* @param device the targeted device
|
||||
* @param mixerAttributes the {@link AudioMixerAttributes} that contains information for
|
||||
* preferred mixer attributes or null if preferred mixer attributes
|
||||
* is cleared
|
||||
*/
|
||||
void onPreferredMixerAttributesChanged(
|
||||
@NonNull AudioAttributes attributes,
|
||||
@NonNull AudioDeviceInfo device,
|
||||
@Nullable AudioMixerAttributes mixerAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage the {@link OnPreferredMixerAttributesChangedListener} listeners and the
|
||||
* {@link PreferredMixerAttributesDispatcherStub}.
|
||||
*/
|
||||
private final CallbackUtil.LazyListenerManager<OnPreferredMixerAttributesChangedListener>
|
||||
mPrefMixerAttributesListenerMgr = new CallbackUtil.LazyListenerManager();
|
||||
|
||||
/**
|
||||
* Adds a listener for being notified of changes to the preferred mixer attributes.
|
||||
* @param executor the executor to execute the callback
|
||||
* @param listener the listener to be notified of changes in the preferred mixer attributes.
|
||||
*/
|
||||
public void addOnPreferredMixerAttributesChangedListener(
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull OnPreferredMixerAttributesChangedListener listener) {
|
||||
Objects.requireNonNull(executor);
|
||||
Objects.requireNonNull(listener);
|
||||
mPrefMixerAttributesListenerMgr.addListener(executor, listener,
|
||||
"addOnPreferredMixerAttributesChangedListener",
|
||||
() -> new PreferredMixerAttributesDispatcherStub());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a previously added listener of changes to the preferred mixer attributes.
|
||||
* @param listener the listener to be notified of changes in the preferred mixer attributes,
|
||||
* which were added via {@link #addOnPreferredMixerAttributesChangedListener(
|
||||
* Executor, OnPreferredMixerAttributesChangedListener)}.
|
||||
*/
|
||||
public void removeOnPreferredMixerAttributesChangedListener(
|
||||
@NonNull OnPreferredMixerAttributesChangedListener listener) {
|
||||
Objects.requireNonNull(listener);
|
||||
mPrefMixerAttributesListenerMgr.removeListener(listener,
|
||||
"removeOnPreferredMixerAttributesChangedListener");
|
||||
}
|
||||
|
||||
private final class PreferredMixerAttributesDispatcherStub
|
||||
extends IPreferredMixerAttributesDispatcher.Stub
|
||||
implements CallbackUtil.DispatcherStub {
|
||||
|
||||
@Override
|
||||
public void register(boolean register) {
|
||||
try {
|
||||
if (register) {
|
||||
getService().registerPreferredMixerAttributesDispatcher(this);
|
||||
} else {
|
||||
getService().unregisterPreferredMixerAttributesDispatcher(this);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchPrefMixerAttributesChanged(@NonNull AudioAttributes attr,
|
||||
int deviceId,
|
||||
@Nullable AudioMixerAttributes mixerAttr) {
|
||||
// TODO: If the device is disconnected, we may not be able to find the device with
|
||||
// given device id. We need a better to carry the device information via binder.
|
||||
AudioDeviceInfo device = getDeviceForPortId(deviceId, GET_DEVICES_OUTPUTS);
|
||||
if (device == null) {
|
||||
Log.d(TAG, "Drop preferred mixer attributes changed as the device("
|
||||
+ deviceId + ") is disconnected");
|
||||
return;
|
||||
}
|
||||
mPrefMixerAttributesListenerMgr.callListeners(
|
||||
(listener) -> listener.onPreferredMixerAttributesChanged(
|
||||
attr, device, mixerAttr));
|
||||
}
|
||||
}
|
||||
|
||||
//====================================================================
|
||||
// Mute await connection
|
||||
|
||||
private final Object mMuteAwaitConnectionListenerLock = new Object();
|
||||
|
||||
@GuardedBy("mMuteAwaitConnectionListenerLock")
|
||||
|
||||
18
media/java/android/media/AudioMixerAttributes.aidl
Normal file
18
media/java/android/media/AudioMixerAttributes.aidl
Normal file
@@ -0,0 +1,18 @@
|
||||
/* Copyright 2022, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
|
||||
package android.media;
|
||||
|
||||
parcelable AudioMixerAttributes;
|
||||
183
media/java/android/media/AudioMixerAttributes.java
Normal file
183
media/java/android/media/AudioMixerAttributes.java
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.media;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Class to represent the attributes of the audio mixer: its format, which represents by an
|
||||
* {@link AudioFormat} object and mixer behavior.
|
||||
*/
|
||||
public final class AudioMixerAttributes implements Parcelable {
|
||||
|
||||
/**
|
||||
* Constant indicating the audio mixer behavior will follow the default platform behavior, which
|
||||
* is mixing all audio sources in the mixer.
|
||||
*/
|
||||
public static final int MIXER_BEHAVIOR_DEFAULT = 0;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(flag = false, prefix = "MIXER_BEHAVIOR_", value = {
|
||||
MIXER_BEHAVIOR_DEFAULT }
|
||||
)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface MixerBehavior {}
|
||||
|
||||
private final AudioFormat mFormat;
|
||||
private final @MixerBehavior int mMixerBehavior;
|
||||
|
||||
/**
|
||||
* Constructor from {@link AudioFormat} and mixer behavior
|
||||
*/
|
||||
AudioMixerAttributes(AudioFormat format, @MixerBehavior int mixerBehavior) {
|
||||
mFormat = format;
|
||||
mMixerBehavior = mixerBehavior;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the format of the audio mixer. The format is an {@link AudioFormat} object, which
|
||||
* includes encoding format, sample rate and channel mask or channel index mask.
|
||||
* @return the format of the audio mixer.
|
||||
*/
|
||||
@NonNull
|
||||
public AudioFormat getFormat() {
|
||||
return mFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mixer behavior for this set of mixer attributes.
|
||||
*
|
||||
* @return the mixer behavior
|
||||
*/
|
||||
public @MixerBehavior int getMixerBehavior() {
|
||||
return mMixerBehavior;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for {@link AudioMixerAttributes} objects.
|
||||
*/
|
||||
public static final class Builder {
|
||||
private final AudioFormat mFormat;
|
||||
private int mMixerBehavior = MIXER_BEHAVIOR_DEFAULT;
|
||||
|
||||
/**
|
||||
* Constructs a new Builder with the defaults.
|
||||
*
|
||||
* @param format the {@link AudioFormat} for the audio mixer.
|
||||
*/
|
||||
public Builder(@NonNull AudioFormat format) {
|
||||
Objects.requireNonNull(format);
|
||||
mFormat = format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines all attributes that have been set and returns a new {@link AudioMixerAttributes}
|
||||
* object.
|
||||
* @return a new {@link AudioMixerAttributes} object
|
||||
*/
|
||||
public @NonNull AudioMixerAttributes build() {
|
||||
AudioMixerAttributes ama = new AudioMixerAttributes(mFormat, mMixerBehavior);
|
||||
return ama;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mixer behavior for the audio mixer
|
||||
* @param mixerBehavior must be {@link #MIXER_BEHAVIOR_DEFAULT}.
|
||||
* @return the same Builder instance.
|
||||
*/
|
||||
public @NonNull Builder setMixerBehavior(@MixerBehavior int mixerBehavior) {
|
||||
switch (mixerBehavior) {
|
||||
case MIXER_BEHAVIOR_DEFAULT:
|
||||
mMixerBehavior = mixerBehavior;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid mixer behavior " + mixerBehavior);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mFormat, mMixerBehavior);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
AudioMixerAttributes that = (AudioMixerAttributes) o;
|
||||
return (mFormat.equals(that.mFormat)
|
||||
&& (mMixerBehavior == that.mMixerBehavior));
|
||||
}
|
||||
|
||||
private String mixerBehaviorToString(@MixerBehavior int mixerBehavior) {
|
||||
switch (mixerBehavior) {
|
||||
case MIXER_BEHAVIOR_DEFAULT:
|
||||
return "default";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new String("AudioMixerAttributes:"
|
||||
+ " format:" + mFormat.toString()
|
||||
+ " mixer behavior:" + mixerBehaviorToString(mMixerBehavior));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeParcelable(mFormat, flags);
|
||||
dest.writeInt(mMixerBehavior);
|
||||
}
|
||||
|
||||
private AudioMixerAttributes(@NonNull Parcel in) {
|
||||
mFormat = in.readParcelable(AudioFormat.class.getClassLoader(), AudioFormat.class);
|
||||
mMixerBehavior = in.readInt();
|
||||
}
|
||||
|
||||
public static final @NonNull Parcelable.Creator<AudioMixerAttributes> CREATOR =
|
||||
new Parcelable.Creator<AudioMixerAttributes>() {
|
||||
/**
|
||||
* Rebuilds an AudioMixerAttributes previously stored with writeToParcel().
|
||||
* @param p Parcel object to read the AudioMixerAttributes from
|
||||
* @return a new AudioMixerAttributes created from the data in the parcel
|
||||
*/
|
||||
public AudioMixerAttributes createFromParcel(Parcel p) {
|
||||
return new AudioMixerAttributes(p);
|
||||
}
|
||||
|
||||
public AudioMixerAttributes[] newArray(int size) {
|
||||
return new AudioMixerAttributes[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2434,4 +2434,40 @@ public class AudioSystem
|
||||
* Keep in sync with core/jni/android_media_DeviceCallback.h.
|
||||
*/
|
||||
final static int NATIVE_EVENT_ROUTING_CHANGE = 1000;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Query the mixer attributes that can be set as preferred mixer attributes for the given
|
||||
* device.
|
||||
*/
|
||||
public static native int getSupportedMixerAttributes(
|
||||
int deviceId, @NonNull List<AudioMixerAttributes> mixerAttrs);
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Set preferred mixer attributes for a given device when playing particular
|
||||
* audio attributes.
|
||||
*/
|
||||
public static native int setPreferredMixerAttributes(
|
||||
@NonNull AudioAttributes attributes,
|
||||
int portId,
|
||||
int uid,
|
||||
@NonNull AudioMixerAttributes mixerAttributes);
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Get preferred mixer attributes that is previously set via
|
||||
* {link #setPreferredMixerAttributes}.
|
||||
*/
|
||||
public static native int getPreferredMixerAttributes(
|
||||
@NonNull AudioAttributes attributes, int portId,
|
||||
List<AudioMixerAttributes> mixerAttributesList);
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Clear preferred mixer attributes that is previously set via
|
||||
* {@link #setPreferredMixerAttributes}
|
||||
*/
|
||||
public static native int clearPreferredMixerAttributes(
|
||||
@NonNull AudioAttributes attributes, int portId, int uid);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.media.AudioDeviceAttributes;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioFocusInfo;
|
||||
import android.media.AudioHalVersionInfo;
|
||||
import android.media.AudioMixerAttributes;
|
||||
import android.media.AudioPlaybackConfiguration;
|
||||
import android.media.AudioRecordingConfiguration;
|
||||
import android.media.AudioRoutesInfo;
|
||||
@@ -37,6 +38,7 @@ import android.media.ICommunicationDeviceDispatcher;
|
||||
import android.media.IDeviceVolumeBehaviorDispatcher;
|
||||
import android.media.IMuteAwaitConnectionCallback;
|
||||
import android.media.IPlaybackConfigDispatcher;
|
||||
import android.media.IPreferredMixerAttributesDispatcher;
|
||||
import android.media.IRecordingConfigDispatcher;
|
||||
import android.media.IRingtonePlayer;
|
||||
import android.media.IStrategyPreferredDevicesDispatcher;
|
||||
@@ -573,4 +575,14 @@ interface IAudioService {
|
||||
boolean handlesvolumeAdjustment);
|
||||
|
||||
AudioHalVersionInfo getHalVersion();
|
||||
|
||||
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS)")
|
||||
int setPreferredMixerAttributes(
|
||||
in AudioAttributes aa, int portId, in AudioMixerAttributes mixerAttributes);
|
||||
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS)")
|
||||
int clearPreferredMixerAttributes(in AudioAttributes aa, int portId);
|
||||
void registerPreferredMixerAttributesDispatcher(
|
||||
IPreferredMixerAttributesDispatcher dispatcher);
|
||||
oneway void unregisterPreferredMixerAttributesDispatcher(
|
||||
IPreferredMixerAttributesDispatcher dispatcher);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.media;
|
||||
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioMixerAttributes;
|
||||
|
||||
/**
|
||||
* AIDL for AudioService to signal preferred mixer attributes update.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
oneway interface IPreferredMixerAttributesDispatcher {
|
||||
|
||||
void dispatchPrefMixerAttributesChanged(
|
||||
in AudioAttributes attributes,
|
||||
int deviceId,
|
||||
in @nullable AudioMixerAttributes mixerAttributes);
|
||||
|
||||
}
|
||||
@@ -1998,4 +1998,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
return mDeviceInventory.getDeviceSensorUuid(device);
|
||||
}
|
||||
}
|
||||
|
||||
void dispatchPreferredMixerAttributesChangedCausedByDeviceRemoved(AudioDeviceInfo info) {
|
||||
// Currently, only media usage will be allowed to set preferred mixer attributes
|
||||
mAudioService.dispatchPreferredMixerAttributesChanged(
|
||||
new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA).build(),
|
||||
info.getId(),
|
||||
null /*mixerAttributes*/);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioDeviceAttributes;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioDevicePort;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
@@ -532,6 +533,18 @@ public class AudioDeviceInventory {
|
||||
.set(MediaMetrics.Property.STATE,
|
||||
wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED
|
||||
? MediaMetrics.Value.DISCONNECTED : MediaMetrics.Value.CONNECTED);
|
||||
AudioDeviceInfo info = null;
|
||||
if (wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED
|
||||
&& AudioSystem.DEVICE_OUT_ALL_USB_SET.contains(
|
||||
wdcs.mAttributes.getInternalType())) {
|
||||
for (AudioDeviceInfo deviceInfo : AudioManager.getDevicesStatic(
|
||||
AudioManager.GET_DEVICES_OUTPUTS)) {
|
||||
if (deviceInfo.getInternalType() == wdcs.mAttributes.getInternalType()) {
|
||||
info = deviceInfo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
synchronized (mDevicesLock) {
|
||||
if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
|
||||
&& DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(type)) {
|
||||
@@ -556,6 +569,11 @@ public class AudioDeviceInventory {
|
||||
if (type == AudioSystem.DEVICE_OUT_HDMI) {
|
||||
mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
|
||||
}
|
||||
if (wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED
|
||||
&& AudioSystem.DEVICE_OUT_ALL_USB_SET.contains(
|
||||
wdcs.mAttributes.getInternalType())) {
|
||||
mDeviceBroker.dispatchPreferredMixerAttributesChangedCausedByDeviceRemoved(info);
|
||||
}
|
||||
sendDeviceConnectionIntent(type, wdcs.mState,
|
||||
wdcs.mAttributes.getAddress(), wdcs.mAttributes.getName());
|
||||
updateAudioRoutes(type, wdcs.mState);
|
||||
|
||||
@@ -88,6 +88,7 @@ import android.media.AudioFormat;
|
||||
import android.media.AudioHalVersionInfo;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioManagerInternal;
|
||||
import android.media.AudioMixerAttributes;
|
||||
import android.media.AudioPlaybackConfiguration;
|
||||
import android.media.AudioRecordingConfiguration;
|
||||
import android.media.AudioRoutesInfo;
|
||||
@@ -104,6 +105,7 @@ import android.media.ICommunicationDeviceDispatcher;
|
||||
import android.media.IDeviceVolumeBehaviorDispatcher;
|
||||
import android.media.IMuteAwaitConnectionCallback;
|
||||
import android.media.IPlaybackConfigDispatcher;
|
||||
import android.media.IPreferredMixerAttributesDispatcher;
|
||||
import android.media.IRecordingConfigDispatcher;
|
||||
import android.media.IRingtonePlayer;
|
||||
import android.media.ISpatializerCallback;
|
||||
@@ -376,6 +378,7 @@ public class AudioService extends IAudioService.Stub
|
||||
private static final int MSG_FOLD_UPDATE = 49;
|
||||
private static final int MSG_RESET_SPATIALIZER = 50;
|
||||
private static final int MSG_NO_LOG_FOR_PLAYER_I = 51;
|
||||
private static final int MSG_DISPATCH_PREFERRED_MIXER_ATTRIBUTES = 52;
|
||||
|
||||
/** Messages handled by the {@link SoundDoseHelper}. */
|
||||
/*package*/ static final int SAFE_MEDIA_VOLUME_MSG_START = 1000;
|
||||
@@ -6566,6 +6569,20 @@ public class AudioService extends IAudioService.Stub
|
||||
handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
|
||||
}
|
||||
|
||||
private static void sendBundleMsg(Handler handler, int msg,
|
||||
int existingMsgPolicy, int arg1, int arg2, Object obj, Bundle bundle, int delay) {
|
||||
if (existingMsgPolicy == SENDMSG_REPLACE) {
|
||||
handler.removeMessages(msg);
|
||||
} else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final long time = SystemClock.uptimeMillis() + delay;
|
||||
Message message = handler.obtainMessage(msg, arg1, arg2, obj);
|
||||
message.setData(bundle);
|
||||
handler.sendMessageAtTime(message, time);
|
||||
}
|
||||
|
||||
boolean checkAudioSettingsPermission(String method) {
|
||||
if (callingOrSelfHasAudioSettingsPermission()) {
|
||||
return true;
|
||||
@@ -8531,6 +8548,10 @@ public class AudioService extends IAudioService.Stub
|
||||
mPlaybackMonitor.ignorePlayerIId(msg.arg1);
|
||||
break;
|
||||
|
||||
case MSG_DISPATCH_PREFERRED_MIXER_ATTRIBUTES:
|
||||
onDispatchPreferredMixerAttributesChanged(msg.getData(), msg.arg1);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (msg.what >= SAFE_MEDIA_VOLUME_MSG_START) {
|
||||
// msg could be for the SoundDoseHelper
|
||||
@@ -10995,6 +11016,125 @@ public class AudioService extends IAudioService.Stub
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AudioManager#setPreferredMixerAttributes(
|
||||
* AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)
|
||||
*/
|
||||
public int setPreferredMixerAttributes(AudioAttributes attributes,
|
||||
int portId, AudioMixerAttributes mixerAttributes) {
|
||||
Objects.requireNonNull(attributes);
|
||||
Objects.requireNonNull(mixerAttributes);
|
||||
if (!checkAudioSettingsPermission("setPreferredMixerAttributes()")) {
|
||||
return AudioSystem.PERMISSION_DENIED;
|
||||
}
|
||||
final int uid = Binder.getCallingUid();
|
||||
final int pid = Binder.getCallingPid();
|
||||
int status = AudioSystem.SUCCESS;
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
final String logString = TextUtils.formatSimple(
|
||||
"setPreferredMixerAttributes u/pid:%d/%d attr:%s mixerAttributes:%s portId:%d",
|
||||
uid, pid, attributes.toString(), mixerAttributes.toString(), portId);
|
||||
sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
|
||||
|
||||
status = mAudioSystem.setPreferredMixerAttributes(
|
||||
attributes, portId, uid, mixerAttributes);
|
||||
if (status == AudioSystem.SUCCESS) {
|
||||
dispatchPreferredMixerAttributesChanged(attributes, portId, mixerAttributes);
|
||||
} else {
|
||||
Log.e(TAG, TextUtils.formatSimple("Error %d in %s)", status, logString));
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AudioManager#clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)
|
||||
*/
|
||||
public int clearPreferredMixerAttributes(AudioAttributes attributes, int portId) {
|
||||
Objects.requireNonNull(attributes);
|
||||
if (!checkAudioSettingsPermission("clearPreferredMixerAttributes()")) {
|
||||
return AudioSystem.PERMISSION_DENIED;
|
||||
}
|
||||
final int uid = Binder.getCallingUid();
|
||||
final int pid = Binder.getCallingPid();
|
||||
int status = AudioSystem.SUCCESS;
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
final String logString = TextUtils.formatSimple(
|
||||
"clearPreferredMixerAttributes u/pid:%d/%d attr:%s",
|
||||
uid, pid, attributes.toString());
|
||||
sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
|
||||
|
||||
status = mAudioSystem.clearPreferredMixerAttributes(attributes, portId, uid);
|
||||
if (status == AudioSystem.SUCCESS) {
|
||||
dispatchPreferredMixerAttributesChanged(attributes, portId, null /*mixerAttr*/);
|
||||
} else {
|
||||
Log.e(TAG, TextUtils.formatSimple("Error %d in %s)", status, logString));
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void dispatchPreferredMixerAttributesChanged(
|
||||
AudioAttributes attr, int deviceId, AudioMixerAttributes mixerAttr) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(KEY_AUDIO_ATTRIBUTES, attr);
|
||||
bundle.putParcelable(KEY_AUDIO_MIXER_ATTRIBUTES, mixerAttr);
|
||||
sendBundleMsg(mAudioHandler, MSG_DISPATCH_PREFERRED_MIXER_ATTRIBUTES, SENDMSG_QUEUE,
|
||||
deviceId, 0, null, bundle, 0);
|
||||
}
|
||||
|
||||
final RemoteCallbackList<IPreferredMixerAttributesDispatcher> mPrefMixerAttrDispatcher =
|
||||
new RemoteCallbackList<IPreferredMixerAttributesDispatcher>();
|
||||
private static final String KEY_AUDIO_ATTRIBUTES = "audio_attributes";
|
||||
private static final String KEY_AUDIO_MIXER_ATTRIBUTES = "audio_mixer_attributes";
|
||||
|
||||
/** @see AudioManager#addOnPreferredMixerAttributesChangedListener(
|
||||
* Executor, AudioManager.OnPreferredMixerAttributesChangedListener)
|
||||
*/
|
||||
public void registerPreferredMixerAttributesDispatcher(
|
||||
@Nullable IPreferredMixerAttributesDispatcher dispatcher) {
|
||||
if (dispatcher == null) {
|
||||
return;
|
||||
}
|
||||
mPrefMixerAttrDispatcher.register(dispatcher);
|
||||
}
|
||||
|
||||
/** @see AudioManager#removeOnPreferredMixerAttributesChangedListener(
|
||||
* AudioManager.OnPreferredMixerAttributesChangedListener)
|
||||
*/
|
||||
public void unregisterPreferredMixerAttributesDispatcher(
|
||||
@Nullable IPreferredMixerAttributesDispatcher dispatcher) {
|
||||
if (dispatcher == null) {
|
||||
return;
|
||||
}
|
||||
mPrefMixerAttrDispatcher.unregister(dispatcher);
|
||||
}
|
||||
|
||||
protected void onDispatchPreferredMixerAttributesChanged(Bundle data, int deviceId) {
|
||||
final int nbDispathers = mPrefMixerAttrDispatcher.beginBroadcast();
|
||||
final AudioAttributes attr = data.getParcelable(
|
||||
KEY_AUDIO_ATTRIBUTES, AudioAttributes.class);
|
||||
final AudioMixerAttributes mixerAttr = data.getParcelable(
|
||||
KEY_AUDIO_MIXER_ATTRIBUTES, AudioMixerAttributes.class);
|
||||
for (int i = 0; i < nbDispathers; i++) {
|
||||
try {
|
||||
mPrefMixerAttrDispatcher.getBroadcastItem(i)
|
||||
.dispatchPrefMixerAttributesChanged(attr, deviceId, mixerAttr);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Can't call dispatchPrefMixerAttributesChanged() "
|
||||
+ "IPreferredMixerAttributesDispatcher "
|
||||
+ mPrefMixerAttrDispatcher.getBroadcastItem(i).asBinder(), e);
|
||||
}
|
||||
}
|
||||
mPrefMixerAttrDispatcher.finishBroadcast();
|
||||
}
|
||||
|
||||
private final Object mExtVolumeControllerLock = new Object();
|
||||
private IAudioPolicyCallback mExtVolumeController;
|
||||
private void setExtVolumeController(IAudioPolicyCallback apc) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioDeviceAttributes;
|
||||
import android.media.AudioMixerAttributes;
|
||||
import android.media.AudioSystem;
|
||||
import android.media.ISoundDoseCallback;
|
||||
import android.media.audiopolicy.AudioMix;
|
||||
@@ -504,6 +505,36 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
|
||||
return AudioSystem.registerSoundDoseCallback(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as
|
||||
* {@link AudioSystem#setPreferredMixerAttributes(
|
||||
* AudioAttributes, int, int, AudioMixerAttributes)}
|
||||
* @param attributes
|
||||
* @param mixerAttributes
|
||||
* @param uid
|
||||
* @param portId
|
||||
* @return
|
||||
*/
|
||||
public int setPreferredMixerAttributes(
|
||||
@NonNull AudioAttributes attributes,
|
||||
int portId,
|
||||
int uid,
|
||||
@NonNull AudioMixerAttributes mixerAttributes) {
|
||||
return AudioSystem.setPreferredMixerAttributes(attributes, portId, uid, mixerAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link AudioSystem#clearPreferredMixerAttributes(AudioAttributes, int, int)}
|
||||
* @param attributes
|
||||
* @param uid
|
||||
* @param portId
|
||||
* @return
|
||||
*/
|
||||
public int clearPreferredMixerAttributes(
|
||||
@NonNull AudioAttributes attributes, int portId, int uid) {
|
||||
return AudioSystem.clearPreferredMixerAttributes(attributes, portId, uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of AudioService dump
|
||||
* @param pw
|
||||
|
||||
Reference in New Issue
Block a user