AAOS CarMediaService 问题分析

文章目录

      • 问题描述
      • 车载蓝牙音乐流程
      • Music 监听焦点变化流程
      • BT请求焦点的流程
      • MediaSession 服务端的流程
      • BT和music 之间的相互影响

问题描述

  • 问题

    AAOS界面连接蓝牙的情况下,Music应用播放音乐会暂停。

  • 分析
    暂停是应用的行为,Music应用会监听focus的变化,监听到焦点失去的情况会调用暂停。但是Music 应用刚启动播放的时候 也会请求焦点,焦点第一次是在bt这边的。bt失去焦点,但立马又重新请求了焦点。 BT请求焦点就导致Music应用失去焦点而暂停。

了解问题之前首先要理解

  1. 蓝牙音乐在哪里调用到音频框架?
  2. 音频焦点
  3. carMediaService

车载蓝牙音乐流程

  • A2DP端

    代码位置:
    system\bt\btif\src\btif_avrcp_audio_track.cc
    system\bt\stack\a2dp\a2dp_aac.cc
    在上述的BtifAvrcpAudioTrackCreate()函数中。在这里面会创建一个AAudioStreamBuilder, AAudio 通过legacy的模式 的audiotrack 来进行处理 写数据。

  • AAudio端

    蓝牙音乐, 车载端是一个sink 端,作为播放来使用。 对应的流程在btif_avrcp_audio_track 是通过调用AAudio的接口来实现播放。其中AAudio 没有实现mmap的方式 走的是legacy模式
    代码在frameworks\av\media\libaaudio\src\legacy\AudioStreamTrack.cpp中
    也就是通过创建audiotrack,然后设置参数、往里面写数据实现的。audiotrack start 的时候 同样会getoutputAttr获取到设备, 这个时候的路由信息已经由AAOS 根据car_audio_policy.xml
    进行注册。

  • 整体流程:

    从source端(也就是手机通过蓝牙)发送过来的是aac或者ldac编码的数据, 数据在a2dp中继续解码 并不是进入到audiotrack。 a2dp中有相当于播放器中解码器的功能,解码后的数据才调用audiotrak进行播放。

Music 监听焦点变化流程

  • 实现AudioFocusListener 然后注册到AudioMananger
   private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {public void onAudioFocusChange(int focusChange) {mMediaplayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();}};mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
  • 在框架层焦点发生变化的时候 回调到外部注册进去的listener

  • Music 中MediaPlayBackService的实现

通过looper发送消息进行处理,而如果是focus丢失的时候,所做的操作是pause。

case AudioManager.AUDIOFOCUS_LOSS:
Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");if (isPlaying()) {mPausedByTransientLossOfFocus = false;}pause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");if (isPlaying()) {mPausedByTransientLossOfFocus = true;}pause();
break;

BT请求焦点的流程

  • 监听mediassion 的onPrepare事件

有Prepare事件发生后会调用requestFocus。其usage是USAGE_MEDIA。这个会回调到AAOS的CarAudioFocus
进行处理, 其就是根据交互矩阵进行处理的。当前持有的是music应用,在BT 请求焦点后,会发送消息通知music 焦点失去了。
在上面的流程知道 失去焦点后 会调用player的 pause 进行暂停操作。

packages\apps\Bluetooth\src\com\android\bluetooth\avrcpcontroller\AvrcpControllerStateMachine.java

BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
sBluetoothMediaBrowserService.mSession.setCallback(callback);MediaSessionCompat.Callback mSessionCallbacks = new MediaSessionCompat.Callback() {@Overridepublic void onPrepare() {logD("onPrepare");A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();if (a2dpSinkService != null) {a2dpSinkService.requestAudioFocus(mDevice, true);}}    
}private synchronized int requestAudioFocus() {if (DBG) Log.d(TAG, "requestAudioFocus()");// Bluetooth A2DP may carry Music, Audio Books, Navigation, or other sounds so mark content// type unknown.AudioAttributes streamAttributes =new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN).build();// Bluetooth ducking is handled at the native layer at the request of AudioManager.AudioFocusRequest focusRequest =new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).setAudioAttributes(streamAttributes).setOnAudioFocusChangeListener(mAudioFocusListener, this).build();int focusRequestStatus = mAudioManager.requestAudioFocus(focusRequest);// If the request is granted begin streaming immediately and schedule an upgrade.if (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {startFluorideStreaming();mAudioFocus = AudioManager.AUDIOFOCUS_GAIN;}return focusRequestStatus;}
  • 那现在的问题是 哪里触发了onPrepare

可以看到是实现了MediaSession的Callback。 理解MediaSession的概念
MediaSession 有客户端和服务端。 客户端对应的是UI 这一端,服务端对应的是player。

UI这一端的实现是在packages/app/Car/Media 中。
主要是几个类 的封装分别是
MediaControl:用来控制MediaSession, 在上面的AVrcpControl中 实现的MessaionCompat的callback回调的
play prepare都是由MediaControl这边调用的。

packages\apps\Car\Media\src\com\android\car\media\service\MediaConnectorService.java
packages\apps\Car\libs\car-media-common\src\com\android\car\media\common\playback\PlaybackViewModel.java

 MediaControllerCompat.TransportControls controls = controller.getTransportControls();
controls.prepare();

这里的prepare 会调用到AvrcpControllerStateMachine的onPrepare中。

  • 如何监听到playstate的变化的?

packages\apps\Music\src\com\android\music\MediaPlaybackService.java
可以确认是music 发送的play state 导致 carMusicApp这边MediaControl调用prepare的.
下面的RemoteControlClient实际是MediaSession的封装

    private void notifyChange(String what) {Intent i = new Intent(what);i.putExtra("id", Long.valueOf(getAudioId()));i.putExtra("artist", getArtistName());i.putExtra("album", getAlbumName());i.putExtra("track", getTrackName());i.putExtra("playing", isPlaying());sendStickyBroadcast(i);if (what.equals(PLAYSTATE_CHANGED)) {mRemoteControlClient.setPlaybackState(isPlaying()? RemoteControlClient.PLAYSTATE_PLAYING: RemoteControlClient.PLAYSTATE_PAUSED);}

代码位置:
packages\services\Car\service\src\com\android\car\CarMediaService.java

是通过重新编写MediaController.Callback来实现的。MediaSession设置的状态变化会通过callback调用到MediaCotroller当中

  private class MediaControllerCallback extends MediaController.Callback {public void onPlaybackStateChanged(@Nullable PlaybackState state) {setPrimaryMediaSource(mediaSource, MEDIA_SOURCE_MODE_PLAYBACK);}
}private void startMediaConnectorService(boolean startPlayback, UserHandle currentUser) {Intent serviceStart = new Intent(MEDIA_CONNECTION_ACTION);Log.d(CarLog.TAG_MEDIA, Log.getStackTraceString(new Throwable()));serviceStart.setPackage(mContext.getResources().getString(R.string.serviceMediaConnection));serviceStart.putExtra(EXTRA_AUTOPLAY, startPlayback);mContext.startForegroundServiceAsUser(serviceStart, currentUser);}

packages\apps\Car\Media\src\com\android\car\media\service\MediaConnectorService.java
在上述的startMediaConnectorService会启动一个service 这个service调用到MediaConnectorSerive中onStartCommand。
在startcommad 中会使用mediacontrol 进行prepare操作。

    public int onStartCommand(Intent intent, int flags, int startId) {playbackViewModel.getPlaybackStateWrapper().observe(this,playbackStateWrapper -> {if (playbackStateWrapper != null) {// If the source to play was specified in the intent ignore others.ComponentName intentComp = mCurrentTask.mMediaComp;ComponentName stateComp = playbackStateWrapper.getMediaSource().getBrowseServiceComponentName();if (!Objects.equals(stateComp, intentComp)) {return;}if (playbackStateWrapper.isPlaying()) {stopTask();return;}if ((playbackStateWrapper.getSupportedActions()& PlaybackStateCompat.ACTION_PREPARE) != 0) {playbackViewModel.getPlaybackController().getValue().prepare();if (!autoplay) {stopTask();}}if (autoplay && (playbackStateWrapper.getSupportedActions()& PlaybackStateCompat.ACTION_PLAY) != 0) {playbackViewModel.getPlaybackController().getValue().play();stopTask();}}});

src\com\android\bluetooth\a2dpsink\A2dpSinkService.java

总结: 单单看MediaSession 和 MediaControl。 MediaControl是UI端控制Service端的类,在AAOS中所有的app播放控制客户端的实现都是carMediaApp中MediaControl的实现的(包括蓝牙audio localplayer界面中暂停播放,下一首 上一首等等)。 MediaSession是服务端, 这个服务端包括(蓝牙的src\com\android\bluetooth
,和/apps/Car/LocalMediaPlayer)。这这里面实现了Mediassion 的callback 用来响应client 端UI的控制。 而响应之后的状态改变可以通过继承MediaControl的callback 在客户端实现。

而Music应用中会向session发送状态改变的消息,客户端carMediaApp会响应这个消息,响应这个消息的结果就是prepare播放器。这个prepare调用到蓝牙的MediaSeesion。MediaSeeion正常应该 一个客户端和服务器一一对应的。

  • 问题的解决

    对于没有MediaSource的session变化 不启动MediaConnectService。

MediaSession 服务端的流程

首先实现MediaSessionCompat.Callback(),然后将这个callback 设置到MediaBrowserService的sesseion中。

BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
sBluetoothMediaBrowserService.mSession.setCallback(callback);

session 中token的传递

  • 为什么music 发送的消息 这边的session 可以接收到。
    因为在CarMediaService 中已经注册了MediaSession变化的消息。 在音乐应用启动的时候 会新建MediaSession,
    而在这里就会监听了active MediaSession的变化,同时传递当前所有的Mediacontrol, 然后对这些mediaControl注册callback。
    在这个callback 中监听onPlaybackStateChanged事件。 而在这边对mediacontrol的管理是通过token实现的。
    token 是在MediaSession 和 MdiaControl 直接建立连接的数据。可以通过getHashCode来打印其hash值确认。
private void initUser(@UserIdInt int userId) {updateMediaSessionCallbackForCurrentUser();if (mSessionsListener != null) {mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsListener);}mSessionsListener = new SessionChangedListener(ActivityManager.getCurrentUser());UserHandle currentUserHandle = new UserHandle(ActivityManager.getCurrentUser());mMediaSessionManager.addOnActiveSessionsChangedListener(null, currentUserHandle,new HandlerExecutor(mHandler), mSessionsListener);}private class SessionChangedListener implements OnActiveSessionsChangedListener {private final int mCurrentUser;SessionChangedListener(int currentUser) {mCurrentUser = currentUser;}@Overridepublic void onActiveSessionsChanged(List<MediaController> controllers) {if (ActivityManager.getCurrentUser() != mCurrentUser) {Slog.e(CarLog.TAG_MEDIA, "Active session callback for old user: " + mCurrentUser);return;}Log.d(CarLog.TAG_MEDIA, Log.getStackTraceString(new Throwable()));Log.d(CarLog.TAG_MEDIA,  "controllers szie " + controllers.size());mMediaSessionUpdater.registerCallbacks(controllers);}}private void registerCallbacks(List<MediaController> newControllers) {List<MediaController> additions = new ArrayList<>(newControllers.size());Map<MediaSession.Token, MediaControllerCallback> updatedCallbacks =new HashMap<>(newControllers.size());for (MediaController controller : newControllers) {MediaSession.Token token = controller.getSessionToken();String newPackageName = controller.getPackageName();Log.d(CarLog.TAG_MEDIA, Log.getStackTraceString(new Throwable()));MediaControllerCallback callback = mCallbacks.get(token);if (callback == null) {callback = new MediaControllerCallback(controller);callback.register();additions.add(controller);}updatedCallbacks.put(token, callback);}private MediaControllerCallback(MediaController mediaController) {public void onPlaybackStateChanged(@Nullable PlaybackState state) {}

BT和music 之间的相互影响

  • 本地音乐在播放,手机播放蓝牙音乐 两个声音同时播放

这个是焦点管理的问题, 按理理解蓝牙音乐播放的时候 应该去请求焦点。需要设置属性才会请求,默认不会请求。
播放的时候 前面流程不用管,最后会调用到SRC_STR_START中,没有请求焦点,music应用就会一直播。修改方法:

解决方法:通过配置shouldRequestFocus 让BT应用在每次播放的时候都强制请求焦点解决。强制请求之后,music就会失去焦点暂停。

packages\apps\Bluetooth\src\com\android\bluetooth\a2dpsink\A2dpSinkStreamHandler.javaswitch (message.what) {case SRC_STR_START:mStreamAvailable = true;if (isTvDevice() || shouldRequestFocus()) {requestAudioFocusIfNone();}break;
  • 蓝牙音乐在播放的时候,本地音乐播放会导致蓝牙直接stop掉。

这个stop 是在蓝牙的MediaSessions callback onstop中调用的。
而这个回调是在CarMediaService中被触发的。也是在上面的流程中music应用的playbackstate 回调中stop的。

解决方法:不调用stop, 调用pause进行暂停。

public void onPlaybackStateChanged(@Nullable PlaybackState state)private void setPlaybackMediaSource(ComponentName playbackMediaSource) {stopAndUnregisterCallback();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/124087.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GE IS420UCSBH1A 控制器模块

控制器模块是工业自动化和控制系统中的关键组件&#xff0c;用于监测、控制和管理各种工程过程。这些模块通常具有以下特点&#xff1a; 多通道控制&#xff1a; 控制器模块通常可以控制多个通道&#xff0c;允许同时管理多个设备或过程。 实时控制&#xff1a; 模块支持实时控…

docker 启动简单的开发环境(mysql, redis, etcd)

docker开启容器分为两种&#xff0c;一种是命令启动&#xff0c;一种是用yaml启动 本片文章用到的是yaml启动 以下是启动脚本&#xff1a;env.yaml version: "3" services:jump_etcd:container_name: jump_etcdimage: bitnami/etcd:3privileged: truevolumes:- &q…

判读文本编码是否为UTF8

&#xff08;----转载-----&#xff0c;忘记出处了抱歉&#xff09; private bool IsUtf8(byte[] buff) { for (int i 0; i < buff.Length; i) { if ((buff[i] & 0xE0) 0xC0) // 110x xxxx 10xx xxxx …

使用NVIDIA GPU FFmpeg转码 YUV to H264(成功)

0. 官方教程 NVIDIA官方教程&#xff1a;链接&#xff0c;本篇内容主要参考2.2 Software Setup。 1. 安装显卡驱动 确保nvidia-smi能够正常使用&#xff1a; 2. 安装CUDA toolkit 注意要与显卡驱动版本对应&#xff0c;验证toolkit是否正确安装&#xff1a; 3. 安装ffnvco…

Pytorch detach()方法

detach() 是 PyTorch 中的一个方法&#xff0c;用于从计算图中分离&#xff08;detach&#xff09;张量。它可以将一个张量从当前计算图中分离出来&#xff0c;返回一个新的张量&#xff0c;该张量与原始张量共享相同的底层数据&#xff0c;但不再追踪梯度信息。 当你需要在计…

代码版本控制工具GitLab :从安装到使用一步到位

一、GitLab 是什么&#xff1f; 如果听说过 Git 或者 GitHub&#xff0c;那么 GitLab 你一定也听说过。GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用 Git 作为代码管理工具&#xff0c;并在此基础上搭建起来的 Web 服务。简单理解&#xff1a;GitLab 类似私人版 …

ssm框架

SSM框架是一个用于构建Java Web应用程序的集成框架&#xff0c;它由三个关键组件组成&#xff0c;分别是Spring、Spring MVC、和MyBatis&#xff0c;这三个组件的作用如下&#xff1a; Spring&#xff08;Spring Framework&#xff09;&#xff1a;Spring是一个轻量级的开源框架…

计算机毕业设计选题推荐-社区志愿者服务微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

Transformers实战(二)快速入门文本相似度、检索式对话机器人

Transformers实战&#xff08;二&#xff09;快速入门文本相似度、检索式对话机器人 1、文本相似度 1.1 文本相似度简介 文本匹配是一个较为宽泛的概念&#xff0c;基本上只要涉及到两段文本之间关系的&#xff0c;都可以被看作是一种文本匹配的任务&#xff0c; 只是在具体…

【表面缺陷检测】铝型材表面缺陷检测数据集介绍(含xml标签文件)

一、铝型材介绍 铝型材是一种由铝合金材料制成的&#xff0c;具有固定截面形状和尺寸的条形建材。由于其优良的物理性能和广泛的应用领域&#xff0c;铝型材在现代工业和生活中发挥着重要的作用。 1、铝型材的分类 根据截面形状的不同&#xff0c;铝型材可分为角铝、槽铝、工…

frp内网穿透教程搭建0.52.3版本

网上很多关于frp的教程都是04 03版本的了&#xff0c;都是配置的ini文件&#xff0c;现在都改成toml文件了&#xff0c;下面基本上都是官方文档的简单copy&#xff0c;细节推荐打开去看中文版的文档介绍&#xff08;地址放在最后了&#xff09;。下面简单介绍几个 为什么使用 …

CAN接口的PCB Layout规则要求汇总

随着时代高速发展&#xff0c;控制器局域网&#xff08;CAN&#xff09;接口的应用越来越广泛&#xff0c;尤其是在汽车电子、航空航天等领域中发挥着重要作用&#xff0c;为了确保CAN接口的可靠性和稳定性&#xff0c;工程师必须在其PCB Layout方面下功夫&#xff0c;下面来看…

【题解】[GenshinOI Round 3] P9816 少项式复合幂

题目链接 分析 首先这题给了很大的提示信息 注意 m 和 p 的范围 , 很自然的想到可以先把所有可能的 f ( x ) f(x) f(x) 算出来. 思维误区 有些人在算完 f ( x ) f(x) f(x) 之后可能就会去思考找环的问题&#xff0c;然后一些码力弱的大佬就会祭掉. 在经过仔细的观察之后…

1496. 判断路径是否相交

1496. 判断路径是否相交 java代码&#xff1a; class Solution {public boolean isPathCrossing(String path) {int x 0;int y 0;HashSet<String> hashSet new HashSet<>();hashSet.add("0-0");for (int i 0; i < path.length(); i) {switch (pa…

客户中心模拟(Queue and A, ACM/ICPC World Finals 2000, UVa822)rust解法

你的任务是模拟一个客户中心运作情况。客服请求一共有n&#xff08;1≤n≤20&#xff09;种主题&#xff0c;每种主题用5个整数描述&#xff1a;tid, num, t0, t, dt&#xff0c;其中tid为主题的唯一标识符&#xff0c;num为该主题的请求个数&#xff0c;t0为第一个请求的时刻&…

centos遇到的问题

lsof -i :8091 > 查看这个端口的线程 lsof &#xff1a; list open files 列出打开文件 -i &#xff1a; internet linux检测系统进程和服务&#xff1a; top &#xff1a; 实时监视系统的进程和资源的利用情况htop &#xff1a; top的增强版 问题&#xff1a; -bash: …

气膜场馆里面噪声很大怎么解决?

随着气膜结构在各个领域的广泛应用&#xff0c;人们开始意识到在这些场馆内部&#xff0c;特别是在大型活动和展览中&#xff0c;噪声问题可能会变得相当严重。传统的气膜结构通常难以提供良好的声学环境&#xff0c;这对于参与者的舒适度和活动的质量构成了挑战。为了解决气膜…

vue3使用@vitejs/plugin-vue-jsx来满足jsx

vue3使用vitejs/plugin-vue-jsx来满足jsx jsx在vue3的使用语法 jsx在vue2种使用 1、安装插件vitejs/plugin-vue-jsx npm install --save-dev vitejs/plugin-vue-jsx2、在 vite.config.js 中添加插件 import { defineConfig } from vite import vue from vitejs/plugin-vue impo…

网站如何判断请求是来自手机-移动端还是PC-电脑端?如何让网站能适应不同的客户端?

如果网站需要实现手机和PC双界面适应&#xff0c;可以有两种方式&#xff1a; 第一种是响应式界面&#xff0c;根据屏幕宽度来判定显示的格式。这种需要前端来做&#xff0c;手机/PC共用一套代码&#xff0c;有一定的局限性。 第二种是后端通过request请求头中的内容来分析客户…

内网穿透实现在外远程访问NAS威联通(QNAP)

文章目录 前言1. 威联通安装cpolar内网穿透2. 内网穿透2.1 创建隧道2.2 测试公网远程访问 3. 配置固定二级子域名3.1 保留二级子域名3.2 配置二级子域名 4. 使用固定二级子域名远程访问 前言 购入威联通NAS后&#xff0c;很多用户对于如何在外在公网环境下的远程访问威联通NAS…