I have a problem now, I developed a player on Android TV, when I simulate using the APP on the mobile phone, the mobile phone speaker can play the sound normally, but when I use the APP on the Android TV box, it is not Can't get audio to my TV speakers via HDMI. At the same time, when I use the native app on the TV box to play video, the sound can be streamed to the TV's speakers via HDMI for playback.
In my code, the following permissions are declared:
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
I have a method called initAudioManager defined as follows:
private void initAudioManager() {
CustomSetting audioModeSetting = customSettingMapper.selectCustomSettingByKey(CustomSettingByKey.AUDIO_OUTPUT_MODE.getKey());
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
if (audioManager == null) {
Log.e(TAG, "initAudioManager: audioManager is null");
return;
}
if (audioModeSetting == null) {
audioManager.setParameters("audio_devices_out_active=AUDIO_CODEC");
Log.i(TAG, "initAudioManager: audioModeSetting is null,set audio_devices_out_active=AUDIO_CODEC");
}
if (audioModeSetting != null) {
if (audioModeSetting.getSettingValue() == 0L) {
audioManager.setParameters("audio_devices_out_active=AUDIO_CODEC");
Log.i(TAG, "initAudioManager: audioModeSetting.getSettingValue() == 0L,set audio_devices_out_active=AUDIO_CODEC");
} else {
audioManager.setParameters("audio_devices_out_active=AUDIO_HDMI");
Log.i(TAG, "initAudioManager: audioModeSetting.getSettingValue() == 1L,set audio_devices_out_active=AUDIO_HDMI");
}
}
Log.i(TAG, "initAudioManager: " + audioManager.getParameters("audio_devices_out_active"));
Log.i(TAG, "initAudioManager: " + Arrays.toString(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)));
MediaRouter mediaRouter = (MediaRouter) getSystemService(Context.MEDIA_ROUTER_SERVICE);
if (mediaRouter == null) {
Log.e(TAG, "initAudioManager: mediaRouter is null");
return;
}
MediaRouter.RouteInfo selectedRoute = mediaRouter.getSelectedRoute(ROUTE_TYPE_LIVE_AUDIO);
Log.d(TAG, "initAudioManager: " + selectedRoute.getName());
}
When the audioModeSetting.getSettingValue() == 0L condition is not met, I will set the device audio output mode to HDMI, but it has no effect.
There is a place to print the log in my code, which is written like this:
Log.i(TAG, "initAudioManager: " + audioManager.getParameters("audio_devices_out_active"));
But when my program runs, the log printed here is empty and nothing.
What should I do to get my audio data to pass through the HDMI cable to the speakers of the TV?
Related
Subject
startBluetoothSco do not work when audio mode was set to MODE_IN_COMMUNICATION firstly by other app on android 12 or higher.
Question
I want to let startBluetoothSco work even when it has been set to MODE_IN_COMMUNICATION firstly by another app
Steps
My test steps are as follows:
Connect the device with a Bluetooth headset,
open the first app, let the app set the audio mode to MODE_IN_COMMUNICATION, and keep the recorder or player running. Code example:
AudioManager audioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
44100, AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
11000);
mAudioRecord.startRecording();
Open the second app, and still set the audio mode to AudioManager.MODE_IN_COMMUNICATION, by the way, startBluetoothSco.
Then the second can not receive any Bluetooth broadcast and SCO does not works.
However, when I kill the first app and rerun the second app, SCO works.
Analysis
According the source code, here is the code:
private CommunicationRouteClient topCommunicationRouteClient() {
for (CommunicationRouteClient crc : mCommunicationRouteClients) {
if (crc.getPid() == mModeOwnerPid) {
return crc;
}
}
if (!mCommunicationRouteClients.isEmpty() && mModeOwnerPid == 0) {
return mCommunicationRouteClients.get(0);
}
return null;
}
when the first app sets the mode to MODE_IN_COMMUNICATION and when will become the mode owner
the second app runs startBluetoothSco, and will get null client, and then will do not start SCO.
private void onUpdateCommunicationRoute(String eventSource) {
AudioDeviceAttributes preferredCommunicationDevice = preferredCommunicationDevice();
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG,
"onUpdateCommunicationRoute, preferredCommunicationDevice: " +
preferredCommunicationDevice +
" eventSource: " +
eventSource);
}
AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
"onUpdateCommunicationRoute, preferredCommunicationDevice: " +
preferredCommunicationDevice + " eventSource: " + eventSource)));
if (preferredCommunicationDevice == null ||
preferredCommunicationDevice.getType() !=
AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
AudioSystem.setParameters("BT_SCO=off");
} else {
AudioSystem.setParameters("BT_SCO=on");
}
if (preferredCommunicationDevice == null) {
removePreferredDevicesForStrategySync(mCommunicationStrategyId);
removePreferredDevicesForStrategySync(mAccessibilityStrategyId);
} else {
setPreferredDevicesForStrategySync(
mCommunicationStrategyId,
Arrays.asList(preferredCommunicationDevice));
setPreferredDevicesForStrategySync(
mAccessibilityStrategyId,
Arrays.asList(preferredCommunicationDevice));
}
onUpdatePhoneStrategyDevice(preferredCommunicationDevice);
}
I am attempting to implement a Loud Speaker / Earpiece switch in Android 12 using the new AudioManager.setCommunicationDevice in place of the deprecated AudioManager.setSpeakerphoneOn.
The result of the set call is true, but the device does not appear to update (both functionally and by checking AudioManager.getCommunicationDevice before and after).
The application is selected as the default Phone package, AudioManager mode is set to AudioManager.MODE_IN_CALL and the manifest includes
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
Below is a code snippet which shows the effect of trying to just set loud speaker during an active call.
AudioDeviceInfo loudSpeakerAudioDevice = null;
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.setMode(AudioManager.MODE_IN_CALL);
List<AudioDeviceInfo> audioDevices = audioManager.getAvailableCommunicationDevices();
for (AudioDeviceInfo audioDevice : audioDevices)
{
if (audioDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)
{
loudSpeakerAudioDevice = audioDevice;
}
}
Log.i("Demo", "Current AudioDevice = " + audioManager.getCommunicationDevice().toString());
if (audioManager.getCommunicationDevice().getType() == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE)
{
Log.i("Demo", "Setting to loudSpeakerAudioDevice = " + loudSpeakerAudioDevice.toString());
if (audioManager.setCommunicationDevice(loudSpeakerAudioDevice) == false)
{
Log.e("Demo", "Failed to set audio device");
}
Log.i("Demo", "New AudioDevice = " + audioManager.getCommunicationDevice().toString());
}
The output of this shows the device ID is the same before and after the set (21).
2022-07-27 10:30:01.465 27907-27907/? I/Demo: Current AudioDevice = android.media.AudioDeviceInfo#21
2022-07-27 10:30:01.501 27907-27907/? I/Demo: Setting to loudSpeakerAudioDevice = android.media.AudioDeviceInfo#22
2022-07-27 10:30:01.518 27907-27907/? I/Demo: New AudioDevice = android.media.AudioDeviceInfo#21
how to play audio through the phone speaker using MediaPlayer while the user is on a phone call through BT headphones.
I tried this:
...
AudioDeviceInfo audioDeviceInfo = getPhoneSpeaker(context);
if (audioDeviceInfo != null) {
setPreferredDevice(audioDeviceInfo);
}
...
#RequiresApi(Build.VERSION_CODES.M)
public AudioDeviceInfo getPhoneSpeaker(Context context) {
AudioDeviceInfo audioDeviceInfo = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
AudioManager manager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (manager != null) {
AudioDeviceInfo[] audioDeviceInfos;
audioDeviceInfos = manager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
if (audioDeviceInfos != null) {
for (AudioDeviceInfo adi : audioDeviceInfos) {
if (adi.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
audioDeviceInfo = adi;
}
}
}
}
}
return audioDeviceInfo;
}
This causes audio to play through the phone speaker, but the problem is that when I play audio the call is terminated in headphones and played through the phone speaker.
Any ideas about how I can avoid this conflict?
Audio Can only be played to standard output devices, speakers etc. It is not possible to play sound during a call.
Try this Twilio Audio Switch SDK
Link: Audio Switch Library
SDK: implementation 'com.twilio:audioswitch:$version-SNAPSHOT'
Listener for the devices
audioSwitch.start { audioDevices, selectedDevice ->
// TODO update UI with audio devices
}
Finding available devices
val devices: List<AudioDevice> = audioSwitch.availableAudioDevices
val selectedDevice: AudioDevice? = audioSwitch.selectedAudioDevice
My use case is I have an app in which a user can press a button and it would toggle between external speaker and the wired headset/bluetooth device. If the user is connected to a wired headset or a bluetooth ear piece device, I want to switch the audio output from that wired headset or bluetooth to the external speaker, and then when they click again, to renable the wired headset/bluetooth audio output.
Does anyone know this can be done in Android 10? I tried the following the example in this post but it doesnt consistently work for bluetooth cases. My code is as followed:
if (shouldEnableExternalSpeaker) {
audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
audioManager.isSpeakerphoneOn = true
if (isBlueToothConnected) {
audioManager.stopBluetoothScoOn()
}
} else {
if (isBlueToothConnected) {
audioManager.startBluetoothSco()
}
audioManager.mode = AudioManager.NORMAL
audioManager.isSpeakerphoneOn = false
}
I also have the necessary permission to the user's audio:
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
I think you should split your handling into 3 cases, play sound via:
external device via Bluetooth
wired headset/device
phone speaker
The resulting code could then loke like this.
if(shouldEnableExternalSpeaker) {
if(isBlueToothConnected) {
// 1. case - bluetooth device
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mAudioManager.startBluetoothSco();
mAudioManager.setBluetoothScoOn(true);
} else {
// 2. case - wired device
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn(false);
mAudioManager.setSpeakerphoneOn(false);
}
} else {
// 3. case - phone speaker
mAudioManager.setMode(AudioManager.MODE_NORMAL);
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn(false);
mAudioManager.setSpeakerphoneOn(true);
}
Even though I haven't used the AudioManager recently, this has worked for me in the past.
Lines:
audioManager.isSpeakerphoneOn = true
audioManager.isSpeakerphoneOn = false
will not work. You have to use setMethods:
Case 1 - bluetooth
mAudioManager.startBluetoothSco(); // This method can be used by applications wanting to send and received audio to/from a bluetooth SCO headset while the phone is not in call.
mAudioManager.setBluetoothScoOn(true); // set true to use bluetooth SCO for communications; false to not use bluetooth SCO for communications
Case 2 - wired
mAudioManager.stopBluetoothSco(); // stop wanting to send and receive
mAudioManager.setBluetoothScoOn(false); // turn off bluetooth
mAudioManager.setSpeakerphoneOn(false); //set true to turn on speakerphone; false to turn it off
Case 3 - internal speaker
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn(false);
mAudioManager.setSpeakerphoneOn(true); // turn on speaker phone
You can keep things automatically happening as follows:
public class BluetoothReceiver extends BroadcastReceiver {
private AudioManager localAudioManager;
private static final int STATE_DISCONNECTED = 0x00000000;
private static final String EXTRA_STATE = "android.bluetooth.headset.extra.STATE";
private static final String TAG = "BluetoothReceiver";
private static final String ACTION_BT_HEADSET_STATE_CHANGED = "android.bluetooth.headset.action.STATE_CHANGED";
private static final String ACTION_BT_HEADSET_FORCE_ON = "android.bluetooth.headset.action.FORCE_ON";
private static final String ACTION_BT_HEADSET_FORCE_OFF = "android.bluetooth.headset.action.FORCE_OFF";
#Override
public void onReceive(final Context context, final Intent intent) {
Log.i(TAG,"onReceive - BluetoothBroadcast");
localAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
final String action = intent.getAction();
if (action.equals(ACTION_BT_HEADSET_STATE_CHANGED)) {
final int extraData = intent.getIntExtra(EXTRA_STATE, STATE_DISCONNECTED);
if (extraData == STATE_DISCONNECTED) {
//no headset -> going other modes
localAudioManager.setBluetoothScoOn(false);
localAudioManager.stopBluetoothSco();
localAudioManager.setMode(AudioManager.MODE_NORMAL);
Log.i(TAG, "Bluetooth Headset Off " + localAudioManager.getMode());
Log.i(TAG, "A2DP: " + localAudioManager.isBluetoothA2dpOn() + ". SCO: " + localAudioManager.isBluetoothScoAvailableOffCall());
} else {
localAudioManager.setMode(0);
localAudioManager.setBluetoothScoOn(true);
localAudioManager.startBluetoothSco();
localAudioManager.setMode(AudioManager.MODE_IN_CALL);
Log.i(TAG, "Bluetooth Headset On " + localAudioManager.getMode());
Log.i(TAG, "A2DP: " + localAudioManager.isBluetoothA2dpOn() + ". SCO: " + localAudioManager.isBluetoothScoAvailableOffCall());
}
}
if (action.equals(ACTION_BT_HEADSET_FORCE_ON)) {
localAudioManager.setMode(0);
localAudioManager.setBluetoothScoOn(true);
localAudioManager.startBluetoothSco();
localAudioManager.setMode(AudioManager.MODE_IN_CALL);
Log.i(TAG, "Bluetooth Headset On " + localAudioManager.getMode());
Log.i(TAG, "A2DP: " + localAudioManager.isBluetoothA2dpOn() + ". SCO: " + localAudioManager.isBluetoothScoAvailableOffCall());
}
if (action.equals(ACTION_BT_HEADSET_FORCE_OFF)) {
localAudioManager.setBluetoothScoOn(false);
localAudioManager.stopBluetoothSco();
localAudioManager.setMode(AudioManager.MODE_NORMAL);
Log.i(TAG, "Bluetooth Headset Off " + localAudioManager.getMode());
Log.i(TAG, "A2DP: " + localAudioManager.isBluetoothA2dpOn() + ". SCO: " + localAudioManager.isBluetoothScoAvailableOffCall());
}
}
}
I am trying to play with BLE transmission on my device.
Here is the code I use and the output:
// check BLE support
Log.i(TAG, "BLE supported: " + getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)); // true
// check BLE transmission support
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();
Log.i(TAG, "isMultipleAdvertisementSupported: " + mBluetoothAdapter.isMultipleAdvertisementSupported()); // false
Log.i(TAG, "isOffloadedFilteringSupported: " + mBluetoothAdapter.isOffloadedFilteringSupported()); // false
Log.i(TAG, "isOffloadedScanBatchingSupported: " + mBluetoothAdapter.isOffloadedScanBatchingSupported()); // false
BluetoothLeAdvertiser mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
Log.i(TAG, mBluetoothLeAdvertiser.toString()); //android.bluetooth.le.BluetoothLeAdvertiser#1c51f789
// check BLE transmission support
// android-beacon-library, https://github.com/AltBeacon/android-beacon-library
int result = BeaconTransmitter.checkTransmissionSupported(getApplicationContext());
Log.i(TAG, "ABL checkTransmissionSupported: " + result); // 0
I can not understand why mBluetoothLeAdvertiser is not null, since mBluetoothLeAdvertiser verifies that it is not false:
package android.bluetooth;
// ...
public BluetoothLeAdvertiser getBluetoothLeAdvertiser() {
if (getState() != STATE_ON) {
return null;
}
if (!isMultipleAdvertisementSupported()) {
return null;
}
synchronized(mLock) {
if (sBluetoothLeAdvertiser == null) {
sBluetoothLeAdvertiser = new BluetoothLeAdvertiser(mManagerService);
}
}
return sBluetoothLeAdvertiser;
}
// ...
public boolean isMultipleAdvertisementSupported() {
if (getState() != STATE_ON) return false;
try {
return mService.isMultiAdvertisementSupported();
} catch (RemoteException e) {
Log.e(TAG, "failed to get isMultipleAdvertisementSupported, error: ", e);
}
return false;
}
Welcome to the world of Android, which is both open source and closed source at the same time! Your analysis of the open source BluetoothLeAdvertiser code above is correct. If that code is running on your mobile device, you would not see the output that your test in the top code snippet shows. Conclusion: the code shown in the second snippet must not be what is on the device.
Android device OEMs are free to fork the source code and modify it to make it work with their hardware. In this case, I know that Motorola did the same thing in this code for their Moto X and Moto G devices. These devices return a BluetoothLeAdvertiser despite the fact that isMultipleAdvertisementSupported() returns false. A Motorola engineer explained to me that they changed this because they wanted to support Advertising despite using a BLE chip that could support only one Advertisement at a time. Indeed, I have verified that Motorola devices can advertise, but if you try to get two advertisements going simultaneously, it fails.