首页 > 其他分享 >Android R音频输出问题处理随笔

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

时间:2023-06-19 11:35:18浏览次数:48  
标签:24 10 15 03 音频 state wdcs Android 随笔


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

标签:24,10,15,03,音频,state,wdcs,Android,随笔
From: https://blog.51cto.com/u_16163453/6512019

相关文章

  • 2023年疫情开放后一个Android的面试历程
    2022.11.24在当前公司待了两年多,被离职了,拿了点赔偿金继续面试。薪资期望13-15,趁着快要过年了整理下面试过程。11月面临了人生第一次比较重大的变化:现在的公司因为融资不利,疫情影响,正式被裁了。这一波很伤…所以在得知消息之后,接下来差不多一个月的时间里,自己开始了的频繁的面......
  • 年后上来面试了13家企业Android岗位,面试题整理
    先卖个关子,如果你是面试官,你希望招一个什么样的人进来?如果这个问题搞明白了,那么可以说测试岗位的面试,就变得非常轻松了。按照一般的惯例,面试官都会让你自我介绍,介绍你的项目经验,询问你的技术能力,这些都是常规的问题。我不去说什么固定的范本什么的,我就以我以前面试别人的情景为例,具......
  • Android ImageView 图片自适应问题记录
    困惑了一周的问题,imageview里的图片是后端返回的,包括返回了图片的宽高,开始利用layoutpararams代码里设置了,不清楚为什么没有生效,后来就忘记优化了。在上线前夕,产品提出了这个优化点,满头雾水。当时想的是有没有可能是后端返回的图片的问题,(产品说ios是好的)。。。。无语(自己技术太菜)......
  • Android屏幕适配的几种方案
    前言由于Android设备存在有不同的屏幕尺寸,屏幕分辨率,像素密度,Android应用在开发的过程必须要考虑到屏幕尺寸适配的问题,以保证在不同尺寸的Android设备上都能够正常运行。我们需要利用适配这一个过程把同一张原型图设计的样式尽可能以同样地视觉效果呈现在不同地屏幕上。适配核心问......
  • android连接本地数据库sqlite,实现增删改查
    前言Android应用数据存储简单来说有这么几种:文件存储、SharedPreference存储、SQLite数据库存储、网络服务器存储、ContentProvider等。如果需要存储的数据量大的时候,那么使用文件存储会有很大的弊端,例如:你想修改其中很微小的项就要先读取整个文件的内容,修改后再全部保存,非常耗时。......
  • 2023-06-19 uniapp云打包报错:app-plus.distribute.icons.android.hdpi 文件不存在
    详细报错:[HBuilder]11:02:51.408Manifest.json文件以下节点配置错误,请检查修复[HBuilder]11:02:51.408app-plus.distribute.icons.android.hdpi 文件不存在[HBuilder]11:02:51.408app-plus.distribute.icons.android.xxhdpi 文件不存在[HBuilder]11:02:51.408ap......
  • 一文带你了解Android IO的底层原理
    前言最近在看《Linux内核设计与实现》的时候,就想着要不把知识串联一下吧。聊什么呢?今天先来聊聊AndroidIO的调用链路。说起IO,这可真是一个很复杂的过程,里面涉及了很多内容,先是软件,最后到硬件,用一张图来表示一下吧:本文打算简单得和大伙讨论一下IO的流程。一、应用层作为应用开......
  • Android 架构之 MVI 完全体 | 重新审视 MVVM 之殇,PartialChange & Reducer 来拯救
    作者:唐子玄MVI架构有三大关键词:“唯一可信数据源”+“单向数据流”+“响应式编程”,以及一些关键概念,比如Intent,State。理解这些概念之后,能更轻松地阅读本文。(强烈建议从第一篇开始阅读)引子在上一篇中,用MVI重构了“新闻流”这个业务场景。本篇在此基础上进一步拓展,引入MVI中......
  • 干了8年Android开发熬到年薪40万,突然接到被辞退消息,应该怎么办?
    0136岁Android开发,为公司工作8年,昨天HR说公司不准备续约前天晚上,有个读者给我留言,讲述了他自己比较气愤的一件事,感觉自己委屈又不值。这位朋友不愿意透露姓名,就叫他H先生吧。H先生是典型的学霸,大学也是211的牌子,又是计算机专业。研究生毕业以后就进入了现在的公司当了一名Android......
  • 方法对了,你做1年Android开发能顶别人做10年
    前几天后台有读者问我这样的问题。他在一家互联网公司工作3年了,每天都很忙,事情又多又杂。本想着学习多一些东西也不是坏事,可到头来一无所获,什么都没学会,满腔的热情也被消磨得差不多。三天两头动辞职的念头,但又不知道自己还能做什么,甚至开始后悔:如果当初选择另一个行业,是不是就会好......