海思hi3716c机顶盒接usb摄像头和usb无线耳机时,无线耳机有时没有声音

两个USB设备分别是:
A:USB摄像头带录音功能,但不带放音功能。
B:USB无线耳机是使用USB转2.4G的无线耳机。


具体现象:
1, A,B两者同时插上机顶盒,并开机进入android,此时去播放音乐或电影,声音是从HDMI出来的,并非从无线耳机出来。此时重新插拔一下2.4G无线耳机,声音就会从耳机中出来。
2, 机顶盒上电,进入android系统,然后播放音乐或电影,此时声音从HDMI中出来。这个时候接上USB摄像头,声音还是从HDMI出来。再接上无线耳机,这时候声音却还是从HDMI中出来,此时应该要从耳机出来。重新插拔一下耳机就恢复正常了。


总结现象,基本可以得出一个结论:开机后,先插上USB摄像头再插上2.4G耳麦,声音并不会从耳机出来,只有重新插拔一次USB耳麦后才会正常。


从现象可以看出,出现异常的原因是音频系统没有从摄像头切换到麦克风。


仔细查看日志,发现正常时会有一段下面的打印,不正常时并没有。
V/AudioService( 1582): Broadcast Receiver: Got ACTION_USB_ANLG_HEADSET_PLUG, state = 1
I/AudioPolicyManagerBase( 1059): setDeviceConnectionState() device: 800, state 1, address 
I/AudioPolicyManagerBase( 1059): [setDeviceConnectionState : 245] device already connected: 16777216
V/WiredAccessoryObserver( 1582): Headset UEVENT: {SUBSYSTEM=switch, SWITCH_STATE=1, DEVPATH=/devices/virtual/switch/usb_audio, SEQNUM=1271, ACTION=change, SWITCH_NAME=usb_audio}
V/WiredAccessoryObserver( 1582): newState = 4, headsetState = 4,mHeadsetState = 0
V/WiredAccessoryObserver( 1582): Intent.ACTION_USB_HEADSET_PLUG: state: 1 name: usb_audio


从日志可以看出,AudioService收到了ACTION_USB_ANLG_HEADSET_PLUG广播消息,才能正常。在源码中搜索发出ACTION_USB_ANLG_HEADSET_PLUG广播的地方,查得在WiredAccessoryObserver.java文件中sendIntent()函数调用了。
    private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) {
        .......
                    intent = new Intent(Intent.ACTION_USB_ANLG_HEADSET_PLUG);
                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
                    intent.putExtra("state", state);
                    intent.putExtra("name", headsetName);
                    ActivityManagerNative.broadcastStickyIntent(intent, null);
        .......
    }
此函数被sendIntents()调用,接着被mHandler的handleMessage()调用。
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            sendIntents(msg.arg1, msg.arg2, (String)msg.obj);
            mWakeLock.release();
        }
    };
接着被update()调用
    private synchronized final void update(String newName, int newState) {
        ........
        mHandler.sendMessageDelayed(mHandler.obtainMessage(0,
                                                           mHeadsetState,
                                                           mPrevHeadsetState,
                                                           mHeadsetName),
                                    delay);
    }
接着被updateState()调用,然后被onUEvent()调用。
onUEvent()重载了UEventObserver.java中的对应函数。仔细查看该文件源码可以得知其中有个线程会读取netlink消息,并由对应的Observer去处理。在run()函数中加入打印日志的函数,将uevent打印出来。
        public void run() {
            native_setup();


            byte[] buffer = new byte[1024];
            int len;
            while (true) {
                len = next_event(buffer);
                if (len > 0) {
                    String bufferStr = new String(buffer, 0, len);  // easier to search a String
                    Log.d (TAG,"uevent:"+bufferStr);
                    synchronized (mObservers) {
                        for (int i = 0; i < mObservers.size(); i += 2) {
                            if (bufferStr.indexOf((String)mObservers.get(i)) != -1) {
                                ((UEventObserver)mObservers.get(i+1))
                                        .onUEvent(new UEvent(bufferStr));
                            }
                        }
                    }
                }
            }
        }


改完后编译frameworks/base,将core.jar文件push到机顶盒进行测试,当正常时会有如下打印
D/UEventObserver( 1582): uevent:change@/devices/virtual/switch/usb_audio��ACTION=change��DEVPATH=/devices/virtual/switch/usb_audio��SUBSYSTEM=switch��SWITCH_NAME=usb_audio��SWITCH_STATE=1��SEQNUM=1271��
V/AudioService( 1582): Broadcast Receiver: Got ACTION_USB_ANLG_HEADSET_PLUG, state = 1
I/AudioPolicyManagerBase( 1059): setDeviceConnectionState() device: 800, state 1, address 
I/AudioPolicyManagerBase( 1059): [setDeviceConnectionState : 245] device already connected: 16777216
V/WiredAccessoryObserver( 1582): Headset UEVENT: {SUBSYSTEM=switch, SWITCH_STATE=1, DEVPATH=/devices/virtual/switch/usb_audio, SEQNUM=1271, ACTION=change, SWITCH_NAME=usb_audio}
V/WiredAccessoryObserver( 1582): newState = 4, headsetState = 4,mHeadsetState = 0
V/WiredAccessoryObserver( 1582): Intent.ACTION_USB_HEADSET_PLUG: state: 1 name: usb_audio


从日志可以知道,不正常的原因在于kernel并没有发出uevent。google去查uevent机制,了解到change这个ACTION由KOBJ_CHANGE来控制。根据uevent中SWITCH_NAME和SWITCH_STATE对kernel/下进行搜索,得到drivers/switch/switch_class.c文件,同时也根据KOBJ_CHANGE对drivers下进行搜索,也发现switch/switch_class.c文件,猜测这个文件是关键。在switch_set_state()函数中加入打印,重新编译内核,运行,查看日志,果然发现都调了这里。
void switch_set_state(struct switch_dev *sdev, int state)
{
    ......
    if (sdev->state != state) {
        sdev->state = state;
        ......
    }
    ......
}


继续搜索调用switch_set_state()的地方,发现在sound/usb/card.c的snd_usb_audio_probe()函数中调用了。
static void *snd_usb_audio_probe(struct usb_device *dev,
                 struct usb_interface *intf,
                 const struct usb_device_id *usb_id)
{
    ......
    //switch_set_state(&sdev, STATE_DISCONNECTED);
    switch_set_state(&sdev, STATE_CONNECTED);
    ......
}


可以知道多个usb音频设备通过switch进行管理,一个时刻只使用一个。当带录音功能的usb摄像头插上时,sdev的状态改为已连接。当usb耳麦接上后,同样会调用switch_set_state(),但因为先前已经连了一个usb音频设备,sdev->state已经变为1,不再继续发消息。于是我注释掉判断语句:if (sdev->state != state) ,再次进行测试,发现uevent已经上报了,但声音仍然没有从usb耳麦中出来。
继续回到Android层,查看WiredAccessoryObserver.java中update()函数
    private synchronized final void update(String newName, int newState) {
        ......
        if (LOG) Slog.v(TAG, "newState = "+newState+", headsetState = "+headsetState+","
            + "mHeadsetState = "+mHeadsetState);
        if (mHeadsetState == headsetState || ((h2w_headset & (h2w_headset - 1)) != 0)) {
            Log.e(TAG, "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 >> 2) == 1 && (usb_headset_dgtl >> 3) == 1) {
            Log.e(TAG, "unsetting usb flag");
            usbStateChange = false;
        }
        if (!h2wStateChange && !usbStateChange) {
            Log.e(TAG, "invalid transition, returning ...");
            return;
        }
        .......
}
分析代码可知,由于上报的消息跟上次一样,此函数并未继续执行。
为了简单起见,我想直接在内核加载新的usb音频设备时先将switch断开原来的,再连接新的。于是在snd_usb_audio_probe()函数中,先执行switch_set_state(&sdev, STATE_DISCONNECTED);欺骗系统switch已经断开,然后再执行switch_set_state(&sdev, STATE_CONNECTED);


编译,烧录,测试后发现一切都正常了,录音与放音都正常。当USB耳麦连接时用耳麦进行录音和放音。当USB耳麦不在时用USB摄像头进行录音,由HDMI进行放音。


对这个结果感到很意外。意想之中的状况应该是始终使用后插上的那个设备。查看日志后得知,由摄像头没有放音功能,在open这个设备进行放音的时候会失败,于是就会使用下一个设备,直到找到能放音的设备,查看日志发现这是alsa-lib这么设计的。为何录音也总是优先使用USB耳麦,没有深究,应该也是alsa-lib完成了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值