Android R音频输出问题处理随笔

Android R音频输出问题处理

背景:播放蓝牙音乐时插拔有线耳机,蓝牙音乐无声音

播放蓝牙音乐是指机器作为sink端,手机作为source端连接,手机播放的音乐通过蓝牙avrcp协议传输和播放

Android R上使用的是蓝牙协议栈已经使用了 AAudio, 系统源码路径是 system/bt/btif/src/btif_avrcp_audio_track.cc

93  void* BtifAvrcpAudioTrackCreate(int trackFreq, int bitsPerSample,
94                                  int channelCount) {
95    LOG_VERBOSE(LOG_TAG, "%s Track.cpp: btCreateTrack freq %d bps %d channel %d ",
96                __func__, trackFreq, bitsPerSample, channelCount);
97  
98    AAudioStreamBuilder* builder;
99    AAudioStream* stream;
100    aaudio_result_t result = AAudio_createStreamBuilder(&builder);
101    AAudioStreamBuilder_setSampleRate(builder, trackFreq);
102    AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_FLOAT);
103    AAudioStreamBuilder_setChannelCount(builder, channelCount);
104    AAudioStreamBuilder_setSessionId(builder, AAUDIO_SESSION_ID_ALLOCATE);
105    AAudioStreamBuilder_setPerformanceMode(builder,
106                                           AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
107    AAudioStreamBuilder_setErrorCallback(builder, ErrorCallback, nullptr);
108    result = AAudioStreamBuilder_openStream(builder, &stream);
109    CHECK(result == AAUDIO_OK);
110    AAudioStreamBuilder_delete(builder);
111  
112    BtifAvrcpAudioTrack* trackHolder = new BtifAvrcpAudioTrack;
113    CHECK(trackHolder != NULL);
114    trackHolder->stream = stream;
115    trackHolder->bitsPerSample = bitsPerSample;
116    trackHolder->channelCount = channelCount;
117    trackHolder->bufferLength =
118        trackHolder->channelCount * AAudioStream_getBufferSizeInFrames(stream);
119    trackHolder->buffer = new float[trackHolder->bufferLength]();
120  
121  #if (DUMP_PCM_DATA == TRUE)
122    outputPcmSampleFile = fopen(outputFilename, "ab");
123  #endif
124    s_AudioEngine.trackFreq = trackFreq;
125    s_AudioEngine.channelCount = channelCount;
126  
127    return (void*)trackHolder;
128  }

具体打印可以看

03-15 10:24:44.209  1505  4561 D bt_btif_avrcp_audio_track: BtifAvrcpAudioErrorHandle AAudio Error handle: restart A2dp Sink AudioTrack
03-15 10:24:44.209  1505  4561 D AAudio  : AAudioStream_requestStart(s#8) called --------------
03-15 10:24:44.209  1505  4561 D AAudioStream: setState(s#8) from 2 to 3

前提知识点:

AudioService中会设置有线耳机的连接状态,这个是耳机接上或断连的情况是frameworks会回调给AudioService的,调用函数 setWiredDeviceConnectionState

客制化修改:

在插拔有线耳机的时候会通过音频策略的修改将输出改回默认的Speaker输出

Android 9.0上的修改如下

    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;
            }
            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;
                        }
                    }
                }
            }
            // My Android Patch Start
            if ((device & AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) != 0) {
                int usage = AudioSystem.getForceUse(AudioSystem.FOR_MEDIA);
                if (DEBUG_DEVICES)
                    Log.d(TAG, "getForceUse media:" + usage);
                // 耳机拔出,如果当前是在耳机通道下,恢复至speaker
                if (state == 0 && (usage == AudioSystem.FORCE_HEADPHONES || usage == AudioSystem.FORCE_NONE)) {
                    if (DEBUG_DEVICES)
                        Log.d(TAG, "FORCE_SPEAKER");
                    setForceUseForMedia(AudioSystem.FORCE_SPEAKER);
                }
                // 耳机接入,将通道切换至耳机
                if (state == 1 && usage != AudioSystem.FORCE_HEADPHONES) {
                    if (DEBUG_DEVICES)
                        Log.d(TAG, "FORCE_HEADPHONES");
                    setForceUseForMedia(AudioSystem.FORCE_HEADPHONES);
                }
            }
            // My Android Patch End
            sendDeviceConnectionIntent(device, state, address, deviceName);
            updateAudioRoutes(device, state);
        }
    }

不过Android 11 上对AudioService进行了重构,抽取了AudioDeviceBroker和AudioDeviceInventory两个类出来用来操作device

    public void setWiredDeviceConnectionState(int type,
            @ConnectionState int state, String address, String name,
            String caller) {
        int newConfig = 0;

        mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller);
        // MyPatch
        // ......
    }

之前是在 MyPatch 这个地方将audio force use设置为Speaker或者有线耳机,这个根据是否连接的状态来确定的

断开连接的情况,如果在MyPatch 这个地方将audio force use设置为Speaker,这里就会有问题,因为 mDeviceBroker.setWiredDeviceConnectionState 这里面的操作是异步操作

我们可以往里面去看看

AudioDeviceBroker.java

    /*package*/ void setWiredDeviceConnectionState(int type,
            @AudioService.ConnectionState int state, String address, String name,
            String caller) {
        //TODO move logging here just like in setBluetooth* methods
        synchronized (mDeviceStateLock) {
            mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller);
        }
    }

继续往AudioDeviceInventory.java 可以看到这个是一个异步的msg操作

    /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
                                                  String address, String name, String caller) {
        synchronized (mDevicesLock) {
            int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE);
            mDeviceBroker.postSetWiredDeviceConnectionState(
                    new WiredDeviceConnectionState(type, state, address, name, caller),
                    delay);
            return delay;
        }
    }

最后异步调用到的地方是这里如下这个地方

    /*package*/ void onSetWiredDeviceConnectionState(
                            AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
        AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));

        Log.d(TAG, "onSetWiredDeviceConnectionState:" + wdcs.mType + ", state:" + wdcs.mState);
        
        synchronized (mDevicesLock) {
            if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
                    && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(wdcs.mType)) {
                    // My_Note
                    mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/,
                        "onSetWiredDeviceConnectionState state DISCONNECTED");
            }

            if (!handleDeviceConnection(wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED,
                    wdcs.mType, wdcs.mAddress, wdcs.mName)) {
                // change of connection state failed, bailout
                mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
                        .record();
                return;
            }
            if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) {
                if (DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(wdcs.mType)) {
                    mDeviceBroker.setBluetoothA2dpOnInt(false, false /*fromA2dp*/,
                            "onSetWiredDeviceConnectionState state not DISCONNECTED");
                }
                mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller);
            }
            if (wdcs.mType == AudioSystem.DEVICE_OUT_HDMI) {
                mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
            }
            sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName);
            updateAudioRoutes(wdcs.mType, wdcs.mState);
        }
        mmi.record();
        // My Android Patch Start
        int device = wdcs.mType;
        int state = wdcs.mState;
        Context context = mDeviceBroker.getContext();
        if (context == null) {
            Log.e(TAG, "change wired headphone device check context fail");
            return;
        }
        if ((device & AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) != 0) {
            int usage = AudioSystem.getForceUse(AudioSystem.FOR_MEDIA);
            if (AudioService.DEBUG_DEVICES)
            // 耳机拔出,如果当前是在耳机通道下,恢复至speaker
            if (state == 0 && (usage == AudioSystem.FORCE_HEADPHONES || usage == AudioSystem.FORCE_NONE)) {
                if (AudioService.DEBUG_DEVICES)
                    Log.d(TAG, "[AudioOutput]FORCE_SPEAKER instead of " + usage);
                setForceUseForMedia(AudioSystem.FORCE_SPEAKER);
            }
            // 耳机接入,将通道切换至耳机
            if (state == 1 && usage != AudioSystem.FORCE_HEADPHONES) {
                if (AudioService.DEBUG_DEVICES)
                    Log.d(TAG, "[AudioOutput]FORCE_HEADPHONES instead of " + usage);
                setForceUseForMedia(AudioSystem.FORCE_HEADPHONES);
            }
        }
        // My Android Patch End
    }

可以看到如上Patch代码是在打在了 onSetWiredDeviceConnectionState 函数最后

为什么要在这个地方修改呢

在上述函数 My_Note (mDeviceBroker.setBluetoothA2dpOnInt)这个地方同样会去设置 force use(设为None,取默认输出设备),所以要保证最后的设置是我们设置的,不然在AudioServcie中设置就有异步的问题

所以这个问题就出在了异步的一个操作

具体可以看Audio Device的会回调给 AudioStreamLegacy

void AudioStreamLegacy::onAudioDeviceUpdate(audio_port_handle_t deviceId)
{
    // Device routing is a common source of errors and DISCONNECTS.
    // Please leave this log in place.
    ALOGD("%s() devId %d => %d", __func__, (int) getDeviceId(), (int)deviceId);
    if (getDeviceId() != AAUDIO_UNSPECIFIED && getDeviceId() != deviceId &&
            getState() != AAUDIO_STREAM_STATE_DISCONNECTED) {
        // Note that isDataCallbackActive() is affected by state so call it before DISCONNECTING.
        // If we have a data callback and the stream is active, then ask the data callback
        // to DISCONNECT and call the error callback.
        if (isDataCallbackActive()) {
            ALOGD("onAudioDeviceUpdate() request DISCONNECT in data callback due to device change");
            // If the stream is stopped before the data callback has a chance to handle the
            // request then the requestStop() and requestPause() methods will handle it after
            // the callback has stopped.
            mRequestDisconnect.request();
        } else {
            ALOGD("onAudioDeviceUpdate() DISCONNECT the stream now");
            forceDisconnect();
        }
    }
    setDeviceId(deviceId);
}

并且判断该Stream的使用情况,如果输出设备不一致,则会断开状态连接,这个会触发AduioErrorCallBack

void AudioStreamLegacy::forceDisconnect(bool errorCallbackEnabled) {
    if (getState() != AAUDIO_STREAM_STATE_DISCONNECTED) {
        setState(AAUDIO_STREAM_STATE_DISCONNECTED);
        if (errorCallbackEnabled) {
            maybeCallErrorCallback(AAUDIO_ERROR_DISCONNECTED);
        }
    }
}

以A2dp的log为例

48  void ErrorCallback(AAudioStream* stream, void* userdata, aaudio_result_t error);
49  
50  void BtifAvrcpAudioErrorHandle() {
51    AAudioStreamBuilder* builder;
52    AAudioStream* stream;
53  
54    aaudio_result_t result = AAudio_createStreamBuilder(&builder);
55    AAudioStreamBuilder_setSampleRate(builder, s_AudioEngine.trackFreq);
56    AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_FLOAT);
57    AAudioStreamBuilder_setChannelCount(builder, s_AudioEngine.channelCount);
58    AAudioStreamBuilder_setSessionId(builder, AAUDIO_SESSION_ID_ALLOCATE);
59    AAudioStreamBuilder_setPerformanceMode(builder,
60                                           AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
61    AAudioStreamBuilder_setErrorCallback(builder, ErrorCallback, nullptr);
62    result = AAudioStreamBuilder_openStream(builder, &stream);
63    CHECK(result == AAUDIO_OK);
64    AAudioStreamBuilder_delete(builder);
65  
66    void* handle = btif_a2dp_sink_get_audio_track();
67    BtifAvrcpAudioTrack* trackHolder = static_cast<BtifAvrcpAudioTrack*>(handle);
68  
69    trackHolder->stream = stream;
70  
71    if (trackHolder != NULL && trackHolder->stream != NULL) {
72        LOG_DEBUG(LOG_TAG, "%s AAudio Error handle: restart A2dp Sink AudioTrack", __func__);
73        AAudioStream_requestStart(trackHolder->stream);
74    }
75    s_AudioEngine.thread = nullptr;
76  }
03-15 10:24:44.183  1505  2029 D AudioStreamLegacy: onAudioDeviceUpdate() devId 2 => 43
03-15 10:24:44.183  1505  2029 D AudioStreamLegacy: onAudioDeviceUpdate() DISCONNECT the stream now
03-15 10:24:44.183  1505  2029 D AAudioStream: setState(s#7) from 4 to 13
03-15 10:24:44.184  1505  4561 I AAudio  : AAudioStreamBuilder_openStream() called ----------------------------------------
03-15 10:24:44.184  1505  4561 I AudioStreamBuilder: rate   =  44100, channels  = 2, format   = 5, sharing = SH, dir = OUTPUT
03-15 10:24:44.184  1505  4561 I AudioStreamBuilder: device =      0, sessionId = 0, perfMode = 12, callback: OFF with frames = 0
03-15 10:24:44.184  1505  4561 I AudioStreamBuilder: usage  =      0, contentType = 0, inputPreset = 0, allowedCapturePolicy = 0
03-15 10:24:44.184  1505  4561 I AudioStreamBuilder: privacy sensitive = false
03-15 10:24:44.184  1505  4561 D AudioStreamBuilder: build() MMAP not used because sessionId specified.
03-15 10:24:44.184  1505  4561 D droid.bluetoot: PlayerBase::PlayerBase()
03-15 10:24:44.185  1505  4561 D AudioStreamTrack: open(), request notificationFrames = 0, frameCount = 0
03-15 10:24:44.185  1505  4561 W AudioTrack: createTrack_l(0): AUDIO_OUTPUT_FLAG_FAST denied by client, not shared buffer and transfer = TRANSFER_SYNC
03-15 10:24:44.186   964  1124 I AS.AudioDeviceInventory: handleDeviceConnection(false dev:8 address: name:)
03-15 10:24:44.187   964  1124 I AS.AudioDeviceInventory: deviceKey:0x8:
03-15 10:24:44.187   964  1124 I AS.AudioDeviceInventory: deviceInfo:[DeviceInfo: type:0x8 (headphone) name: addr: codec: 0] is(already)Connected:true
03-15 10:24:44.192   367  1125 D MAudioPolicyManager: setDeviceConnectionState() device: 0x8, state 0, address  name  format 0x0
03-15 10:24:44.192   367  1126 D AudioFlinger: Client defaulted notificationFrames to 1992 for frameCount 3985
03-15 10:24:44.199   367  1126 D AF::TrackHandle: OpPlayAudio: track:76 usage:1 not muted
03-15 10:24:44.201   344   427 D audio_hw_primary: adev_set_parameters: disconnect=8
03-15 10:24:44.201   344   427 D audio_hw_primary: adev_set_parameters: headphone disconnect
03-15 10:24:44.201   344   427 I audio_hw: adev_set_parameters[197](0x0xf54b9240): disconnect=8
03-15 10:24:44.202   344   770 D modules.usbaudio.audio_hal: adev_set_parameters: kvpairs = disconnect=8
03-15 10:24:44.203  1505  4561 D AAudioStream: setState(s#8) from 0 to 2
03-15 10:24:44.203  1505  4561 W AudioStreamTrack: open() flags changed from 0x00000004 to 0x00000000
03-15 10:24:44.203  1505  4561 W AudioStreamTrack: open() perfMode changed from 12 to 10
03-15 10:24:44.208  1505  4561 I AAudio  : AAudioStreamBuilder_openStream() returns 0 = AAUDIO_OK for s#8 ----------------
03-15 10:24:44.209  1505  4561 D bt_btif_avrcp_audio_track: BtifAvrcpAudioErrorHandle AAudio Error handle: restart A2dp Sink AudioTrack
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容