• 海思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完毕了。


  • 相关阅读:
    私有数据
    三大框架的对比
    angular的优化
    teamview centos 配置
    各种语言HMAC SHA256实现
    HttpClient使用cookie
    Mysql 安装
    iOS Safari 中点击事件失效的解决办法
    java验证码Captcha
    js 表单验证
  • 原文地址:https://www.cnblogs.com/llguanli/p/7243303.html
Copyright © 2020-2023  润新知