提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用
实现耳机输入输出的插拔检测功能:修改插拔驱动代码,驱动中上报inputevent事件,通过framework处理事件并发出广播;
平台:amlogic
soc:a311d
输出检测gpio口(headphone):
插拔检测原理就是通过gpio口的电平变化来判断插拔状态,可以通过gpio中断、轮询gpio电平两种方式获取插拔状态的变化。
DTS配置gpio资源:
auge_sound {
compatible = "amlogic, g12a-sound-card";
aml-audio-card,name = "AML-AUGESOUND";
/*avout mute gpio*/
avout_mute-gpios = <&gpio_ao GPIOAO_2 GPIO_ACTIVE_HIGH>;
es7241_rst-gpios = <&gpio_ao GPIOAO_10 GPIO_ACTIVE_HIGH>;
+ /*headphone 插拔检测gpio*/
+ aml-audio-card,hp-det-gpio = <&gpio GPIOA_8 GPIO_ACTIVE_HIGH>;
+ /*micphone 插拔检测gpio*/
+ aml-audio-card,mic-det-gpio = <&gpio GPIOA_7 GPIO_ACTIVE_HIGH>;
......
}
我拿到的驱动代码是通过定时器加工作队列去轮询gpio电平来判断插拔状态的:
static void jack_timer_func(unsigned long data)
{
struct aml_card_data *card_data = (struct aml_card_data *)data;
unsigned long delay = msecs_to_jiffies(150);
schedule_work(&card_data->work);/*每隔150ms去调度一个工作队列,轮询插拔状态*/
mod_timer(&card_data->timer, jiffies + delay);
}
static void jack_work_func(struct work_struct *work)
{
flag = jack_audio_hp_detect(card_data);/*检测gpio电平*/
if (flag == -1)
goto micphone_detect;
if (card_data->hp_detect_flag != flag) {/*如果电平有变化*/
card_data->hp_detect_flag = flag;
if (flag) {
/*方法一:使用uevent,上报同步extcon事件,会更新/sys/class/extcon/xxx/state节点的值;
但是目前上次java还是使用的switch的方式来处理uevent事件,所以上下层不兼容导致该方法无法使用;
不过文件系统下的extcon节点是可以根据插拔改变state值的*/
extcon_set_state_sync(audio_extcon_headphone, EXTCON_JACK_HEADPHONE, 1);
/*方法二:使用inputevent,上报input事件;
我的java层是兼容inputevent的,所以使用此方法上报插拔事件*/
snd_soc_jack_report(&card_data->hp_jack.jack, SND_JACK_HEADPHONE, SND_JACK_HEADPHONE);
}
}
}
static void audio_jack_detect(struct aml_card_data *card_data)
{
init_timer(&card_data->timer);
card_data->timer.function = jack_timer_func;
card_data->timer.data = (unsigned long)card_data;
INIT_WORK(&card_data->work, jack_work_func);
jack_audio_start_timer(card_data, msecs_to_jiffies(5000));
}
这是方法一,实现extcon的API接口及步骤:
static const unsigned int headphone_cable[] = {
EXTCON_JACK_HEADPHONE,
EXTCON_NONE,
};
edev = extcon_dev_allocate(headphone_cable);//创建一个extcon数据结构
if (IS_ERR(edev)) {
pr_info("failed to allocate audio extcon headphone\n");
return;
}
edev->dev.parent = dev;
edev->name = "audio_extcon_headphone";//这将是extcon节点下name的值
dev_set_name(&edev->dev, "headphone");//设置名称,这将会是文件系统下extcon节点的名称
ret = extcon_dev_register(edev);//将extcon注册,最后会生成/sys/class/extcon/xxx/目录,下面包括name、state等文件
if (ret < 0) {
pr_info("failed to register audio extcon headphone\n");
return;
}
audio_extcon_headphone = edev;
extcon_set_state_sync(audio_extcon_headphone, EXTCON_JACK_HEADPHONE, 1);//上报一个extcon事件
这是方法二,实现inputevent的API接口及步骤:
pin_name = "Headphones";
mask = SND_JACK_HEADPHONE;
//生成一个新的jack对象,定义其被检测的类型,即拔插的设备类型
snd_soc_card_jack_new(card, pin_name, mask, &sjack->jack, &sjack->pin, 1);
snd_soc_jack_add_gpios(&sjack->jack, 1, &sjack->gpio);
/*汇报jack插拔状态,主要完成以下两个工作:
a) 根据插入拔出状态更新前面通过snd_soc_jack_add_pins加入的dapm pin的状态,对其进行上电下电管理。
b) 调用snd_jack_report,在其中通过input_report_key/input_report_switch来向上层汇报input event。*/
snd_soc_jack_report(&card_data->mic_jack.jack, SND_JACK_MICROPHONE, SND_JACK_MICROPHONE);
//snd_soc_jack_report(&card_data->mic_jack.jack, 0, SND_JACK_MICROPHONE);
/frameworks/base/core/res/res/values/config.xml
将config_useDevInputEventForAudioJack修改为true才能使用inputevent方式上报插拔事件:
<!-- When true use the linux /dev/input/event subsystem to detect the switch changes on the headphone/microphone jack. When false use the older uevent framework. -->
<bool name="config_useDevInputEventForAudioJack">true</bool>
\frameworks\base\services\core\java\com\android\server\input\InputManagerService.java
当底层有input事件上报后,会调用notifySwitch方法:
// Native callback.
private void notifySwitch(long whenNanos, int switchValues, int switchMask) {
if (DEBUG) {
Slog.d(TAG, "notifySwitch: values=" + Integer.toHexString(switchValues)
+ ", mask=" + Integer.toHexString(switchMask));
}
if ((switchMask & SW_LID_BIT) != 0) {
final boolean lidOpen = ((switchValues & SW_LID_BIT) == 0);
mWindowManagerCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen);
}
if ((switchMask & SW_CAMERA_LENS_COVER_BIT) != 0) {
final boolean lensCovered = ((switchValues & SW_CAMERA_LENS_COVER_BIT) != 0);
mWindowManagerCallbacks.notifyCameraLensCoverSwitchChanged(whenNanos, lensCovered);
}
if (mUseDevInputEventForAudioJack && (switchMask & SW_JACK_BITS) != 0) {
mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues,
switchMask);
}
if ((switchMask & SW_TABLET_MODE_BIT) != 0) {
SomeArgs args = SomeArgs.obtain();
args.argi1 = (int) (whenNanos & 0xFFFFFFFF);
args.argi2 = (int) (whenNanos >> 32);
args.arg1 = Boolean.valueOf((switchValues & SW_TABLET_MODE_BIT) != 0);
mHandler.obtainMessage(MSG_DELIVER_TABLET_MODE_CHANGED,
args).sendToTarget();
}
}
\frameworks\base\services\core\java\com\android\server\WiredAccessoryManager.java
调用notifyWiredAccessoryChanged方法:
@Override
public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask) {
if (LOG) Slog.v(TAG, "notifyWiredAccessoryChanged: when=" + whenNanos
+ " bits=" + switchCodeToString(switchValues, switchMask)
+ " mask=" + Integer.toHexString(switchMask));
synchronized (mLock) {
int headset;
mSwitchValues = (mSwitchValues & ~switchMask) | switchValues;
switch (mSwitchValues &
(SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_LINEOUT_INSERT_BIT)) {
case 0:
headset = 0;
break;
case SW_HEADPHONE_INSERT_BIT:
headset = BIT_HEADSET_NO_MIC;
break;
case SW_LINEOUT_INSERT_BIT:
headset = BIT_LINEOUT;
break;
case SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT:
headset = BIT_HEADSET;
break;
case SW_MICROPHONE_INSERT_BIT:
headset = BIT_HEADSET;
break;
default:
headset = 0;
break;
}
updateLocked(NAME_H2W,
(mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) | headset);
}
}
private void updateLocked(String newName, int newState) {
// Retain only relevant bits
int headsetState = newState & SUPPORTED_HEADSETS;
int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG;
int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL;
int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT);
boolean h2wStateChange = true;
boolean usbStateChange = true;
if (LOG) Slog.v(TAG, "newName=" + newName
+ " newState=" + newState
+ " headsetState=" + headsetState
+ " prev headsetState=" + mHeadsetState);
if (mHeadsetState == headsetState) {
Log.e(TAG, "No state change.");
return;
}
// reject all suspect transitions: only accept state changes from:
// - a: 0 headset to 1 headset
// - b: 1 headset to 0 headset
if (h2w_headset == (BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) {
Log.e(TAG, "Invalid combination, unsetting h2w flag");
h2wStateChange = false;
}
// - c: 0 usb headset to 1 usb headset
// - d: 1 usb headset to 0 usb headset
if (usb_headset_anlg == BIT_USB_HEADSET_ANLG && usb_headset_dgtl == BIT_USB_HEADSET_DGTL) {
Log.e(TAG, "Invalid combination, unsetting usb flag");
usbStateChange = false;
}
if (!h2wStateChange && !usbStateChange) {
Log.e(TAG, "invalid transition, returning ...");
return;
}
mWakeLock.acquire();
Log.i(TAG, "MSG_NEW_DEVICE_STATE");
Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState,
mHeadsetState, "");
mHandler.sendMessage(msg);
mHeadsetState = headsetState;
}
private final Handler mHandler = new Handler(Looper.myLooper(), null, true) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_NEW_DEVICE_STATE:
setDevicesState(msg.arg1, msg.arg2, (String)msg.obj);
mWakeLock.release();
break;
case MSG_SYSTEM_READY:
onSystemReady();
mWakeLock.release();
break;
}
}
};
private void setDevicesState(
int headsetState, int prevHeadsetState, String headsetName) {
synchronized (mLock) {
int allHeadsets = SUPPORTED_HEADSETS;
for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
if ((curHeadset & allHeadsets) != 0) {
setDeviceStateLocked(curHeadset, headsetState, prevHeadsetState, headsetName);
allHeadsets &= ~curHeadset;
}
}
}
}
private void setDeviceStateLocked(int headset,
int headsetState, int prevHeadsetState, String headsetName) {
if ((headsetState & headset) != (prevHeadsetState & headset)) {
int outDevice = 0;
int inDevice = 0;
int state;
if ((headsetState & headset) != 0) {
state = 1;
} else {
state = 0;
}
if (headset == BIT_HEADSET) {
outDevice = AudioManager.DEVICE_OUT_WIRED_HEADSET;
inDevice = AudioManager.DEVICE_IN_WIRED_HEADSET;
} else if (headset == BIT_HEADSET_NO_MIC){
outDevice = AudioManager.DEVICE_OUT_WIRED_HEADPHONE;
} else if (headset == BIT_LINEOUT){
outDevice = AudioManager.DEVICE_OUT_LINE;
} else if (headset == BIT_USB_HEADSET_ANLG) {
outDevice = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET;
} else if (headset == BIT_USB_HEADSET_DGTL) {
outDevice = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET;
} else if (headset == BIT_HDMI_AUDIO) {
outDevice = AudioManager.DEVICE_OUT_HDMI;
} else {
Slog.e(TAG, "setDeviceState() invalid headset type: "+headset);
return;
}
if (LOG) {
Slog.v(TAG, "headsetName: " + headsetName +
(state == 1 ? " connected" : " disconnected"));
}
if (outDevice != 0) {
mAudioManager.setWiredDeviceConnectionState(outDevice, state, "", headsetName);
}
if (inDevice != 0) {
mAudioManager.setWiredDeviceConnectionState(inDevice, state, "", headsetName);
}
}
}
\frameworks\base\media\java\android\media\AudioManager.java
调用setWiredDeviceConnectionState方法:
public void setWiredDeviceConnectionState(int type, int state, String address, String name) {
final IAudioService service = getService();
try {
service.setWiredDeviceConnectionState(type, state, address, name,
mApplicationContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
\frameworks\base\services\core\java\com\android\server\audio\AudioService.java
调用onSetWiredDeviceConnectionState方法:
private void onSetWiredDeviceConnectionState(int device, int state, String address,
String deviceName, String caller) {
if (DEBUG_DEVICES) {
Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device)
+ " state:" + Integer.toHexString(state)
+ " address:" + address
+ " deviceName:" + deviceName
+ " caller: " + caller + ");");
}
synchronized (mConnectedDevices) {
if ((state == 0) && ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {
setBluetoothA2dpOnInt(true, "onSetWiredDeviceConnectionState state 0");
}
if (!handleDeviceConnection(state == 1, device, address, deviceName)) {
// change of connection state failed, bailout
//return;
}
boolean isBluetoothOrUsbOutDevice = isBluetoothOrUsbOutDevices(device);
if (isBluetoothOrUsbOutDevice) {
updateHdmiSystemAudioStatus(state == 0);
}
if (state != 0) {
if ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {
setBluetoothA2dpOnInt(false, "onSetWiredDeviceConnectionState state not 0");
}
if ((device & mSafeMediaVolumeDevices) != 0) {
sendMsg(mAudioHandler,
MSG_CHECK_MUSIC_ACTIVE,
SENDMSG_REPLACE,
0,
0,
caller,
MUSIC_ACTIVE_POLL_PERIOD_MS);
}
// Television devices without CEC service apply software volume on HDMI output
if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
//mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
checkAllFixedVolumeDevices();
if (mHdmiManager != null) {
synchronized (mHdmiManager) {
if (mHdmiPlaybackClient != null) {
mHdmiCecSink = false;
mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
}
}
}
}
if ((device & AudioSystem.DEVICE_OUT_HDMI) != 0) {
sendEnabledSurroundFormats(mContentResolver, true);
}
} else {
if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
if (mHdmiManager != null) {
synchronized (mHdmiManager) {
mHdmiCecSink = false;
}
}
}
}
sendDeviceConnectionIntent(device, state, address, deviceName);
updateAudioRoutes(device, state);
}
}
private void sendDeviceConnectionIntent(int device, int state, String address,
String deviceName) {
if (DEBUG_DEVICES) {
Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) +
" state:0x" + Integer.toHexString(state) + " address:" + address +
" name:" + deviceName + ");");
}
Intent intent = new Intent();
if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
intent.setAction(Intent.ACTION_HEADSET_PLUG);
intent.putExtra("microphone", 1);
Log.d("zjy", "audioservices intent microphone:1 state:"+state);
} else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
device == AudioSystem.DEVICE_OUT_LINE) {
intent.setAction(Intent.ACTION_HEADSET_PLUG);
intent.putExtra("microphone", 0);
Log.d("zjy", "audioservices intent microphone:0 state:"+state);
} else if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
intent.setAction(Intent.ACTION_HEADSET_PLUG);
intent.putExtra("microphone",
AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
== AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
} else if (device == AudioSystem.DEVICE_IN_USB_HEADSET) {
if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
== AudioSystem.DEVICE_STATE_AVAILABLE) {
intent.setAction(Intent.ACTION_HEADSET_PLUG);
intent.putExtra("microphone", 1);
} else {
// do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
return;
}
} else if (device == AudioSystem.DEVICE_OUT_HDMI ||
device == AudioSystem.DEVICE_OUT_HDMI_ARC) {
configureHdmiPlugIntent(intent, state);
}
if (intent.getAction() == null) {
return;
}
intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
/*这个标志位很重要,丫的浪费了我的时间,它表示该广播的接收器只能使用动态注册的方式,不能再xml文件里静态注册*/
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
final long ident = Binder.clearCallingIdentity();
try {
//终于在这儿发送广播了,广播为Intent.ACTION_HEADSET_PLUG,后面在广播接收器中接收该广播
ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
\frameworks\base\core\java\android\app\ActivityManager.java
调用broadcastStickyIntent方法:
/**
* @hide
*/
public static void broadcastStickyIntent(Intent intent, int userId) {
broadcastStickyIntent(intent, AppOpsManager.OP_NONE, userId);
}
/**
* Convenience for sending a sticky broadcast. For internal use only.
*
* @hide
*/
public static void broadcastStickyIntent(Intent intent, int appOp, int userId) {
try {
getService().broadcastIntent(
null, intent, null, null, Activity.RESULT_OK, null, null,
null /*permission*/, appOp, null, false, true, userId);
} catch (RemoteException ex) {
}
}
一路调用下来,终于是完成了广播的发送,现在需要注册一个广播接收器AudioInOutReceiver.java:
package com.droidlogic;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class AudioInOutReceiver extends BroadcastReceiver {
private static final String TAG = "AudioInOutReceiver";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.i(TAG, "action: " + action);
if(Intent.ACTION_HEADSET_PLUG.equals(action)){
Log.i(TAG, "action equals: " + action);
if(intent.hasExtra("microphone") && intent.hasExtra("state")){
if(intent.getIntExtra("microphone", 0) == 1){
if(intent.getIntExtra("state", 0) == 1){
//audio input insert
Log.i(TAG, "audio input insert");
}else if(intent.getIntExtra("state", 0) == 0){
//audio input pull
Log.i(TAG, "audio input pull");
}
}else if(intent.getIntExtra("microphone", 0) == 0){
if(intent.getIntExtra("state", 0) == 1){
//audio output insert
Log.i(TAG, "audio output insert");
}else if(intent.getIntExtra("state", 0) == 0){
//audio output pull
Log.i(TAG, "audio output pull");
}
}
}
}
}
}
在vendor/amlogic/common/frameworks/core/res/AndroidManifest.xml中完成广播接收器的静态注册:
(该方法经测试不行,因为前面讲过,广播有个FLAG_RECEIVER_REGISTERED_ONLY标志,所以只能动态注册广播接收器)
<receiver android:name="com.droidlogic.AudioInOutReceiver">
<intent-filter>
<action android:name="android.media.AudioManager.ACTION_HEADSET_PLUG" />
</intent-filter>
</receiver>
在activity中动态注册广播接收器,这样才能成功接收广播:
package vhd.a311d.zjy;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
AudioInOutReceiver audioInOutReceiver = new AudioInOutReceiver();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/*动态注册广播接收器*/
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_HEADSET_PLUG);
registerReceiver(audioInOutReceiver, filter);
}
@Override
protected void onDestroy()
{
super.onDestroy();
/*销毁广播接收器*/
unregisterReceiver(audioInOutReceiver);
}
}
找个3.5mm的耳机,拔插测试一下:
插拔耳机会更新extcon节点的值:
console:/sys/class/extcon/headphone # cat name
audio_extcon_headphone
#插入headphone
console:/sys/class/extcon/headphone # cat state
HEADPHONE=1
#拔出headphone
console:/sys/class/extcon/headphone # cat state
HEADPHONE=0
console:/sys/class/extcon/microphone # cat name
audio_extcon_microphone
#插入micphone
console:/sys/class/extcon/microphone # cat state
MICROPHONE=1
#拔出micphone
console:/sys/class/extcon/microphone # cat state
MICROPHONE=0
插拔耳机也会上报inputevent事件,广播接收器会接收framework发出的广播:
图片略
Android 耳机插拔处理流程
Android HDMI audio设备插拔事件
耳机插入上层处理流程分析
Android Framework 音频子系统(09)耳麦插拔之流程分析
java代码是真的看的头大,这次算是熟悉了audio inputevent事件在java层的调用过程;如果是使用Uevent方法的话,3.5内核把switch class移除掉了,换成了extcon,java层处理extcon事件后面还需要学习一下;AudioService.java中还提供了诸如调整音量、获取耳机模式等功能,是耳机的大管家。