Android驱动——audio输入输出插拔检测

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用


文章目录

  • 前言
  • 一、audio插拔检测原理图
  • 二、驱动相关代码
  • 三、插拔事件处理并广播
  • 四、测试
  • 总结


前言

实现耳机输入输出的插拔检测功能:修改插拔驱动代码,驱动中上报inputevent事件,通过framework处理事件并发出广播;
平台:amlogic
soc:a311d


一、audio插拔检测原理图

输入检测gpio口(microphone):

Android驱动——audio输入输出插拔检测_第1张图片

输出检测gpio口(headphone):

Android驱动——audio输入输出插拔检测_第2张图片
插拔检测原理就是通过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中还提供了诸如调整音量、获取耳机模式等功能,是耳机的大管家。

你可能感兴趣的:(工作笔记,Android驱动)