我正在寻找一种可靠的方法来处理蓝牙耳机上的音量按钮按下。我的测试设备是小米Mi耳机,但此功能应适用于所有基本耳机。
新近配对的耳机音量按钮正在改变音量,但是只有“内部” ...在Android中设置的音量不会改变,静止不动。在这种情况下,似乎只在耳机上改变了音量-Android上设置的最大值也是耳机上的最大值(例如,设备上的1/10音量=耳机上的最大1/10音量)。当我打开“主”应用程序(已经在处理bt连接)并打开SCO时,我可以使用耳机按钮更改通话音量,Android会在屏幕上显示控件。第二种情况很有意思,但是任何方法都足以处理这些键...
我创建了一个新项目并进行了尝试,
MediaSessionCompat
-VolumeProviderCompat
完全不触发MediaSessionCompat.Callback
,方法onMediaButtonEvent
仅通过开/关/呼叫按钮被调用。此外,当我setBluetoothScoOn(true)
ACTION_VENDOR_SPECIFIC_HEADSET_EVENT
-耳机启动时,我仅收到一两个AT命令(电池信息),而按一下按钮(SCO处于打开和关闭状态)都没有得到任何数据BluetoothSocket
读取,socket.isconnected()
是true
,但是在等待数据的行的下一个socket.getInputStream().read(buffer)
上挂起,从未收到单个字节,超时或异常。 。KeyEvent
s 相关片段-适用于1.和2。:
private void setupMediaSession() {
MediaSessionCompat mediaSession = new MediaSessionCompat(this, "PlayerService");
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 0) //you simulate a player which plays something.
.build());
VolumeProviderCompat volumeProvider = new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, /*max volume*/100, /*initial volume level*/50) {
@Override
public void onAdjustVolume(int direction) {
Timber.i("TESTTEST onAdjustVolume direction: %s", direction);
}
};
mediaSession.setPlaybackToRemote(volumeProvider);
MediaSessionCompat.Callback mMediaSessionCallback = new MediaSessionCompat.Callback() {
@Override
public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
Timber.i("TESTTEST onMediaButtonEvent mediaButtonEvent: %s", mediaButtonEvent.getAction());
return true;
}
};
mediaSession.setCallback(mMediaSessionCallback);
AudioTrack at = new AudioTrack(AudioManager.STREAM_MUSIC, 48000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT,
AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT), AudioTrack.MODE_STREAM);
at.play();
mediaSession.setActive(true);
Timber.i("TESTTEST setupMediaSession isActive: %s", mediaSession.isActive());
}
3。
private void setupVendorReceiver() {
IntentFilter intentFilter = new IntentFilter(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT);
/*intentFilter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY +
'.'+ 911); // Xiaomi*/
for(int i=0; i< 3000; i++) {
intentFilter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY +
'.' + i);
}
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Timber.i("TESTTEST setupVendor onReceive: %s", intent.getAction());
for (String key: intent.getExtras().keySet())
{
Timber.i( "TESTTEST, "+key + ":"+intent.getExtras().get(key));
if(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS.equals(key)){
Object[] args = (Object[]) intent.getExtras().get(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS);
for(Object arg : args){
Timber.i( "TESTTEST, arg: %s", arg);
}
}
}
}
}, intentFilter);
}
for 4。
private void socketConnection() {
Set<BluetoothDevice> bs = BluetoothAdapter.getDefaultAdapter().getBondedDevices();
for (BluetoothDevice device : bs) {
Timber.i("TESTTEST device: %s", device);
try {
socket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
break;
} catch (IOException e) {
Timber.e(e);
}
}
new Thread() {
@Override
public void run() {
InputStream in;
OutputStream out;
try {
socket.connect();
in = socket.getInputStream();
out = socket.getOutputStream();
} catch (IOException e) {
Timber.e(e);
socket = null;
return;
}
while (isRunning) {
try {
Timber.i("TESTTEST socket.isConnected() %s", socket.isConnected());
if (socket.isConnected()) {
int bytesRead = in.read(buffer);
Timber.i("TESTTEST bytesRead %s buffer %s", bytesRead, buffer);
}
} catch (IOException e) {
Timber.e(e);
}
}
}
}.start();
}
5。
@Override
public boolean onKeyDown(final int keyCode, KeyEvent event) {
Timber.i("TESTTEST onKeyDown: %s", keyCode);
return super.onKeyDown(keyCode, event);
}
在上述摘录中打开配对耳机(ACL),然后运行我的应用程序会显示此日志
com.example.myapplication I/MainActivity: TESTTEST onCreate
com.example.myapplication I/BluetoothSoc: TESTTEST device: 1C:52:16:3E:B4:03
com.example.myapplication I/MainActivity: TESTTEST setupMediaSession isActive: true
com.example.myapplication I/BluetoothSoc: TESTTEST socket.isConnected() true
当按下音量键时,别无其他……有什么建议吗?
PS 1.我找到了HD1-R5耳机的一些文档,
PTT仅适用于MobilitySound加密狗,ICOM和Kenwood收音机。
PTT表示音量键或专用键(在普通型号上接听电话)。还发现了HD1-R6文档,它在规格上看起来完全一样,只有一个例外:它不是上面的行,而是具有
PTT仅适用于POCZELLO,ESChat,WAVE,Kodiak,GroupTalk和其他版本以下的Android手机。
还有YT video使用R6,...这两个设备有什么区别?
PS 2.小米支持HFP/A2DP/HSP/AVRCP
配置文件(手动找到here),以上两个MobilitySound耳机都支持HFP,HSP,A2DP
编辑: 接听PS1。-区别在于耳机音量键发送的AT命令。 R5正在发送一些“知名”(例如TETRA doc):
AT+CTKST=0,1
AT+CTKST=0,0
并且R6使用Android文档中在某些示例HERE中记录的命令:
AT+XEVENT=TALK,1
AT+XEVENT=TALK,0
可能没有进行测试,没有任何这些-AT命令取自PR5和PR6,R5和R6的较新版本。仍然常见的小米模型没有以“仅配对模式”(ACL)发送任何内容-如顶部所述,它会更改“内部”音量-并且在SCO开启的情况下,这些AT命令似乎由某些内部命令(本机? )蓝牙控制器,严格地传递给音量控制,并且进一步不传递给套接字/应用程序