1. 程式人生 > >海思hi3716c機頂盒接usb攝像頭和usb無線耳機時,無線耳機有時沒有聲音

海思hi3716c機頂盒接usb攝像頭和usb無線耳機時,無線耳機有時沒有聲音

setup 出現 sea log delayed 重載 出現異常 jar action

兩個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完畢了。


海思hi3716c機頂盒接usb攝像頭和usb無線耳機時,無線耳機有時沒有聲音