精华内容
下载资源
问答
  • 蓝牙音乐播放器

    2015-09-25 21:53:58
    本应用提供了较为友好的音乐播放界面,并可接受远程蓝牙设备发送的以UTF-8编码的,遵守特定协议的歌曲列表信息,实现模式切换,音量调节,歌曲点播等实用功能,旨在为DIY音乐播放器的爱好者提供表现作品特色的...
  • android 车载蓝牙音乐介绍

    千次阅读 热门讨论 2020-11-25 15:37:02
    网上蓝牙音乐相关的文章实在太少,贡献一下自己的微薄之力 先讲一些零碎知识点: ##################################华丽分割线################################### 蓝牙的源码路径 frameworks\base\core\java\...

    网上蓝牙音乐相关的文章实在太少,贡献一下自己的微薄之力

    先讲一些零碎知识点:

    ##################################华丽分割线###################################
    蓝牙的源码路径

    frameworks\base\core\java\android\bluetooth
    

    ##################################华丽分割线###################################
    蓝牙音乐使用中需要用到的权限

    在apk中的AndroidManifest.xml中要有以下语句获得蓝牙相关权限:

      <uses-permission android:name="android.permission.BLUETOOTH" />
      <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
      <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
    

    ##################################华丽分割线 下面介绍蓝牙的广播部分 start###############################

    蓝牙的广播部分

    蓝牙的连接

    注册蓝牙回调,这里需要讲一下BluetoothAdapter、BluetoothAvrcpController、BluetoothA2dpSink三个类
    

    BluetoothAdapter作用:

    获取蓝牙开关状态,搜索蓝牙,配对蓝牙等

    BluetoothAvrcpController作用:

    这个类里主要是维护蓝牙音乐的相关信息更新(ID3),操作控制蓝牙音乐(播放暂停上一曲下一曲等)

    BluetoothA2dpSink 作用:

    这个类里主要是确定蓝牙音乐是否连接上

    注册蓝牙回调广播

        public void registerBtReceiver(Context context) {
            IntentFilter intentFilter = new IntentFilter();
              //A2DP连接状态改变
            intentFilter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
          //A2DP播放状态改变
            intentFilter.addAction(BluetoothA2dpSink.ACTION_PLAYING_STATE_CHANGED);
            //监听蓝牙音乐暂停、播放等 
            intentFilter.addAction(BluetoothAvrcpController.ACTION_TRACK_EVENT);
            //连接状态
            intentFilter.addAction(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
    		//浏览        
            intentFilter.addAction(BluetoothAvrcpController.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
            // 正在浏览的事件 
            intentFilter.addAction(BluetoothAvrcpController.ACTION_BROWSING_EVENT);
            //当前 媒体 项目 改变  
            intentFilter.addAction(BluetoothAvrcpController.ACTION_CURRENT_MEDIA_ITEM_CHANGED);
            intentFilter.addAction(BluetoothAvrcpController.ACTION_PLAYER_SETTING);
            //没有媒体信息
            intentFilter.addAction(BluetoothAvrcpController.ACTION_PLAY_FAILURE);
            intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
            intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
            intentFilter.addAction(BluetoothDevice.ACTION_NAME_CHANGED);
    
            context.registerReceiver(mBtReceiver, intentFilter);
        }
    

    注册完回调以后,会有一个回调函数

        private BroadcastReceiver mBtReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                switch (intent.getAction()) {
                    case BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED:
                    	//todo 主要处理蓝牙a2dp连接状态
                        break;
                    case BluetoothA2dpSink.ACTION_PLAYING_STATE_CHANGED:
                        LogUtil.e(TAG, "mBtReceiver,
                        //控制蓝牙的播放状态,启动这个作为播放状态更新,时序太慢,所以注意不要用这个回调更新播放状态,建议在BluetoothAvrcpController.ACTION_TRACK_EVENT回调中处理播放状态
                        break;
                    case BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED:
                        break;
                    case BluetoothAvrcpController.ACTION_TRACK_EVENT:
                    //处理媒体信息,包括需要显示的MediaMetadata基本信息,和实时更新的PlaybackState信息
                        break;
                    case BluetoothAvrcpController.ACTION_BROWSE_CONNECTION_STATE_CHANGED:
                    // 手机端断开并重新连接上需要更新
                        break;
                    case BluetoothAvrcpController.ACTION_BROWSING_EVENT:
                    //蓝牙音乐列表
                        break;
                    case BluetoothAvrcpController.ACTION_CURRENT_MEDIA_ITEM_CHANGED:
                        //广播得到媒体信息
                        BluetoothAvrcpMediaItemData mMeidaItemData = intent.getParcelableExtra(BluetoothAvrcpController.EXTRA_MEDIA_ITEM_DATA);
                        break;
                    case BluetoothAvrcpController.ACTION_PLAYER_SETTING:
                        break;
                    case BluetoothAvrcpController.ACTION_PLAY_FAILURE:
                    //这个是系统增加的接口,用于提示 当手机端播放器没有打开或者没有播放器的时候,是没有蓝牙音乐相关信息的,考虑到有些只是上层应用用原生的蓝牙多说一下,这种接口上层是没有的
                        break;
                    case BluetoothAdapter.ACTION_STATE_CHANGED:
                    //蓝牙开关状态 但一般不用这个,而是用 BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED 来判断
                        break;
                    case BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED:
                        //用这个广播判断蓝牙连接状态,注意这个是总开关,包含了蓝牙音乐和蓝牙电话
                        break;
                    case BluetoothDevice.ACTION_NAME_CHANGED:
                  	 //检查蓝牙名字,是否更新
                        break;
                }
            }
        };
    

    注册profile回调

      public void registerProfile(Context context) {
            if (BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, profileServiceListener, BluetoothProfile.A2DP_SINK)) {
                LogUtil.i(TAG, "registerProfile: A2DP_SINK success");
            } else {
                LogUtil.e(TAG, "registerProfile: A2DP_SINK failed");
            }
            if (BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, profileServiceListener, BluetoothProfile.AVRCP_CONTROLLER)) {
                LogUtil.i(TAG, "registerProfile: AVRCP_CONTROLLER success");
            } else {
                LogUtil.e(TAG, "registerProfile: AVRCP_CONTROLLER failed");
            }
        }
    

    A2dp和Avrcp的监听,主要是处理一些,应用还未起来,蓝牙已经连接上了,有些广播不走,需要通过这里来处理

    
        //这个类里主要是确定蓝牙音乐是否连接上
        private BluetoothA2dpSink mBluetoothA2dpSink;
        //这个类里主要是维护蓝牙音乐的相关信息更新(ID3),操作控制蓝牙音乐(播放暂停上一曲下一曲等)
        private BluetoothAvrcpController mAvrcpController;
      
        private BluetoothProfile.ServiceListener profileServiceListener = new BluetoothProfile.ServiceListener() {
            @Override
            public void onServiceConnected(int profile, BluetoothProfile proxy) {
                LogUtil.i(TAG, "onServiceConnected: profile=" + profile + ",BluetoothProfile=" + proxy);
                switch (profile) {
                    case BluetoothProfile.A2DP_SINK:
                        mBluetoothA2dpSink = (BluetoothA2dpSink) proxy;
                        LogUtil.e(TAG, "onServiceConnected: mBluetoothA2dpSink=" + mBluetoothA2dpSink);
                       //todo 这里可以做设置蓝牙为可用状态,或者更新设备名字,设置音频焦点
                        break;
                    case BluetoothProfile.AVRCP_CONTROLLER:
                        mAvrcpController = (BluetoothAvrcpController) proxy;
                        LogUtil.e(TAG, "onServiceConnected: mAvrcpController=" + mAvrcpController);
                        //todo 第一次注册,这种情况需要更新播放状态,跟新媒体信息,播放进度
                        break;
                }
            }
    
            @Override
            public void onServiceDisconnected(int profile) {
                LogUtil.i(TAG, "onServiceDisconnected: profile=" + profile);
                switch (profile) {
                    case BluetoothProfile.A2DP_SINK:
                        mBluetoothA2dpSink = null;
                        break;
                    case BluetoothProfile.AVRCP_CONTROLLER:
                        mAvrcpController = null;
                        break;
                }
            }
        };
    

    注销广播,注销监听

        public void unregisterBtReceiver(Context context) {
            if (mBtReceiver != null) {
                context.unregisterReceiver(mBtReceiver);
                mBtReceiver = null;
            }
        }
    
        public void unRegisterProfile() {
            mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, mBluetoothA2dpSink);
            mBluetoothAdapter.closeProfileProxy(BluetoothProfile.AVRCP_CONTROLLER, mAvrcpController);
        }
    

    广播中获取媒体信息和进度条信息

        /**
         * 更新歌曲基本信息ui
         *
         * @param intent 广播回调的intent 在 BluetoothAvrcpController.ACTION_TRACK_EVENT 回调中
         */
        private void updateMediaMetadata(Intent intent) {
            MediaMetadata mediaMetadata = intent.getParcelableExtra(BluetoothAvrcpController.EXTRA_METADATA);
    
            String title = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
            String artist = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
            String album = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ALBUM);
            String genre = mediaMetadata.getString(MediaMetadata.METADATA_KEY_GENRE);
            long totalTime = mediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION); //总时间更新,ms单位
            long currentTrack = mediaMetadata.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS);
            long totalTrack = mediaMetadata.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
        }
    
      /**
         * 主要用来实时更新当前播放进度, 广播回调的intent 在 BluetoothAvrcpController.ACTION_TRACK_EVENT 回调中
         *
         * @param intent
         */
        private void updatePlaybackState(Intent intent) {
            PlaybackState playbackState = intent.getParcelableExtra(BluetoothAvrcpController.EXTRA_PLAYBACK);
     
            //更新播放状态 ,这里可以自己保存一个播放状态的标识
            if ((playbackState.getState() == PlaybackState.STATE_PLAYING)
                    || (playbackState.getState() == PlaybackState.STATE_FAST_FORWARDING)//快进
                    || (playbackState.getState() == PlaybackState.STATE_REWINDING)) {//快退
                updataPlayState(true);
            } else {
                updataPlayState(false);
            }
    
            long currentTime = playbackState.getPosition();//当前时间,ms为单位
        }
    

    用a2dp的连接状态,来判断蓝牙音乐的打开,因为蓝牙有一个总开关,里面包含有用于蓝牙音乐的a2dp通道开关,一个是用于蓝牙电话的,这里主要是标识蓝牙是否可用的状态

        /**
         * 蓝牙a2dp连接状态。在BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED回调中
         *
         * @param intent
         */
        private void btA2dpContentStatus(Intent intent) {
            int a2dpSinkConnectionState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
            switch (a2dpSinkConnectionState) {
                case BluetoothProfile.STATE_CONNECTED:
                //这里重新获取了一下当前的设备信息,有些时候蓝牙断开了,设备信息是会被清空的
                    mConnectedDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                   
                    //蓝牙音乐连接上,设置音源为true,里面会自动播放
                    setAudioStreamMode(true);
    
                    //这里有个特殊处理iphone,前提:iphone 会先走 adapter 后走 BluetoothA2dpSink,所以这再次获取一下设备信息
                    initConnectDevice();
                    break;
                case BluetoothProfile.STATE_DISCONNECTED:
                   // 设置蓝牙为不可用状态,清空对应的一些标示位就行了
                    break;
            }
        }
    

    蓝牙开关连接状态

        /**
         * 蓝牙开关连接状态
         *
         * @param intent
         */
        private void btContentStatus(Intent intent) {
            int currentContentStatus = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, -1);
            switch (currentContentStatus) {
                case BluetoothAdapter.STATE_DISCONNECTED:
                    LogUtil.e(TAG, "蓝牙已经断开连接");
                    //只有蓝牙断开设置才置为null
                    mConnectedDevice = null;
    				//todo 处理一些播放状态,设备名字等,清空操作
                    //蓝牙断开,蓝牙也不应该发声了
                    setAudioStreamMode(false);
                    break;
                case BluetoothAdapter.STATE_CONNECTING:
                    LogUtil.e(TAG, "蓝牙正在连接");
                    break;
                case BluetoothAdapter.STATE_CONNECTED:
                    LogUtil.e(TAG, "蓝牙已经连接");
                    //连接的操作这里就不处理了,蓝牙因为的连接操作,放到 BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED 这个回调中,更准确
                    break;
                case BluetoothAdapter.STATE_DISCONNECTING:
                    LogUtil.e(TAG, "蓝牙正在断开连接");
                    break;
            }
    
        }
    

    检查蓝牙名字,是否更新

      /**
         * 检查蓝牙名字,是否更新,在BluetoothDevice.ACTION_NAME_CHANGED 回调中
         *
         * @param intent
         */
        private void checkBtName(Intent intent) {
            BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            if (bluetoothDevice.equals(mConnectedDevice)) {//地址相等则更新名字
                updataName();
            }
        }
    

    以上就是用到的一些需要在广播回调中处理的一些事情及使用方法

    //####################分割线 蓝牙的广播部分 end#####################
    //####################分割线 下面是需要提供的一些接口 start#####################

    蓝牙接口部分

    获取蓝牙设备的名字

    首先获取BluetoothDevice 蓝牙设备的管理类,通过遍历方式获取

         //蓝牙开关状态,搜索蓝牙,配对蓝牙等
        private BluetoothAdapter mBluetoothAdapter;
        private BluetoothDevice mConnectedDevice = null;//蓝牙连接的设备
        
        public void initConnectDevice() {
            mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
            //mBluetoothAdapter为null概率很低,这里不做判断,系统一启动就会赋值
            Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
            for (BluetoothDevice device : bondedDevices) {
                if (device.isConnected()) {
                    mConnectedDevice = device;
                    LogUtil.e(TAG, "蓝牙连接上的设备:mConnectedDevice=" + mConnectedDevice);
                }
            }
        }
    

    然后根据 mConnectedDevice 获取设备的名字

        /**
         * 获得远端(手机端)已连接的蓝牙设备的名称
         */
        public String getBTDeviceName() {
            return mConnectedDevice.getName();
        }
    

    设置焦点和释放焦点

    //系统没有记录蓝牙音乐是否出声状态,需要自己记录,false不可以出声,这个方法是系统修改的,原生没有算车机端特殊处理的
     private boolean mIsAudioStreamModeOn = false; 
        public void setAudioStreamMode(boolean on) {
            boolean ret = mBluetoothAdapter.setAudioStreamMode(on);
            if (ret) {
                mIsAudioStreamModeOn = on;
            } else {
                mIsAudioStreamModeOn = false;
            }
        }
    

    播放与暂停的使用

        /**
         * 播放与暂停
         */
        public void sendPlayPauseCmd(boolean isAvrcpPlayStatus) {
            if (isAvrcpPlayStatus) {
                mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_PLAY, BluetoothAvrcp.PASSTHROUGH_STATE_PRESS);
                mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_PLAY, BluetoothAvrcp.PASSTHROUGH_STATE_RELEASE);
            } else {
                mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_PAUSE, BluetoothAvrcp.PASSTHROUGH_STATE_PRESS);
                mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_PAUSE, BluetoothAvrcp.PASSTHROUGH_STATE_RELEASE);
            }
        }
    

    上一曲

        /**
         * 上一曲
         */
        public void sendPastCmd() {
                mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_BACKWARD, BluetoothAvrcp.PASSTHROUGH_STATE_PRESS);
                mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_BACKWARD, BluetoothAvrcp.PASSTHROUGH_STATE_RELEASE);
        }
    

    下一曲

       /**
         * 下一曲
         */
        public void sendNextCmd() {
                mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_FORWARD, BluetoothAvrcp.PASSTHROUGH_STATE_PRESS);
                mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_FORWARD, BluetoothAvrcp.PASSTHROUGH_STATE_RELEASE);
        }
    

    获取当前媒体信息

        /**
         * 获取当前媒体信息
         *
         * @return
         */
        public MediaMetadata getCurrentMediaInfo() {
            return mAvrcpController.getMetadata(mConnectedDevice);
        }
    

    获取当前进度

       /**
         * 获取当前进度
         *
         * @return
         */
        public long getCurrentProgress() {
            return mAvrcpController.getPlaybackState(mConnectedDevice) == null ? 0 : mAvrcpController.getPlaybackState(mConnectedDevice).getPosition();
        }
    

    获取当前媒体总时长

        /**
         * 获取当前媒体总时长
         *
         * @return
         */
        public long getCurrentTotleTime() {
            return mAvrcpController.getMetadata(mConnectedDevice) == null ? 0
                    : mAvrcpController.getMetadata(mConnectedDevice).getLong(MediaMetadata.METADATA_KEY_DURATION);
        }
    

    //####################分割线 下面是需要提供的一些接口 end#####################

    以上是我使用到的一些东西,可留言,更新你需要的

    这里有一些东西是隐藏文件,需要编译打开

    项目地址:https://github.com/MironGsony/CarBluetoothMusic
    如何编译android系统的隐藏文件:https://editor.csdn.net/md/?articleId=110139734

    展开全文
  • 2.蓝牙音乐在播放的时候导航界面语音播报蓝牙音乐会暂停,播报结束会恢复播放不能同时输出问题。 3.蓝牙音乐在播放的时候和导航界面的语音播报混音输出的时候会有蓝牙音乐非常频繁且非常短暂卡顿的问题。 涉及的...

    此文章主要解决三个问题

    1.高德导航的时候打电话会出现混音问题。

    2.蓝牙音乐在播放的时候导航界面语音播报蓝牙音乐会暂停,播报结束会恢复播放不能同时输出问题。

    3.蓝牙音乐在播放的时候和导航界面的语音播报混音输出的时候会有蓝牙音乐非常频繁且非常短暂卡顿的问题。

    涉及的路径:

    packages/apps/Bluetooth/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java 
    packages/apps/Bluetooth/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamingStateMachine.java 
    system/bt/btif/src/btif_avrcp_audio_track.cpp 

     

    一,关于蓝牙电话和导航混音的问题

    原因在于导航界面的语音播报是持续的并且是连续的,而且每次导航音频的播报都会有音频焦点抢占和播放的动作。而在7.1蓝牙中,只是对蓝牙电话焦点进行了一次抢占,这样就导致了 导航播报的时候会再次抢占到点形成了混音的问题,需要做的就是在蓝牙电话拨出或者接听的时候将系统的焦点修改为

    mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);

    的方式进行抢占关于Android 7.1 的patch

     

    diff --git a/apps/Bluetooth/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java b/apps/Bluetooth/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
    index 66e25c8..3968429 100755
    --- a/apps/Bluetooth/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
    +++ b/apps/Bluetooth/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
    @@ -330,6 +330,24 @@ final class HeadsetClientStateMachine extends StateMachine {
    if (state == c.getState()) {
    return;
    }
    +
    + if (state == BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) {
    + if (c.getState() == BluetoothHeadsetClientCall.CALL_STATE_WAITING) {
    + Log.d(TAG, "Reject waiting call");
    + } else if (getCall(BluetoothHeadsetClientCall.CALL_STATE_HELD) != null) {
    + Log.d(TAG, "Has held call");
    + } else {
    + if (mAudioManager.getMode() != AudioManager.MODE_NORMAL) {
    + mAudioManager.setMode(AudioManager.MODE_NORMAL);
    + Log.d(TAG, "abandonAudioFocus ");
    + // abandon audio focus after the mode has been set back to normal
    + mAudioManager.abandonAudioFocusForCall();
    + }
    + }
    + }
    +
    c.setState(state);
    sendCallChangedIntent(c);
    Log.d(TAG, "Exit setCallState()");
    @@ -902,6 +920,36 @@ final class HeadsetClientStateMachine extends StateMachine {
    Log.d(TAG, "updateCallsDone new call id:" + entry.getValue().getId());
    sendCallChangedIntent(entry.getValue());
    }
    +
    + if ((getCurrentState() == mAudioOn) && (entry.getValue().getState() == BluetoothHeadsetClientCall.CALL_STATE_ACTIVE)) {
    + Log.d(TAG, "mode is AudioOn");
    + int newAudioMode = AudioManager.MODE_IN_CALL;
    + int currMode = mAudioManager.getMode();
    + if (currMode != newAudioMode) {
    + // request audio focus before setting the new mode
    + mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
    + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
    + Log.d(TAG, "setAudioMode Setting audio mode from "
    + + currMode + " to " + newAudioMode);
    + mAudioManager.setMode(newAudioMode);
    + Log.d(TAG,"hfp_enable=true");
    + Log.d(TAG,"mAudioWbs is " + mAudioWbs);
    + if (mAudioWbs) {
    + Log.d(TAG,"Setting sampling rate as 16000");
    + mAudioManager.setParameters("hfp_set_sampling_rate=16000");
    + }
    + else {
    + Log.d(TAG,"Setting sampling rate as 8000");
    + mAudioManager.setParameters("hfp_set_sampling_rate=8000");
    + }
    + mAudioManager.setParameters("hfp_enable=true");
    + }
    + } else {
    + Log.d(TAG, "mode is NOT AudioOn; Don't change audio mode.");
    + }
    }
    
    mCalls = mCallsUpdate;
    @@ -2098,6 +2146,27 @@ final class HeadsetClientStateMachine extends StateMachine {
    // Ringing is not handled at this indication and rather should be
    // implemented (by the client of this service). Use the
    // CALL_STATE_INCOMING (and similar) handle ringing.
    +
    + int newAudioMode = AudioManager.MODE_RINGTONE;
    + int currMode = mAudioManager.getMode();
    + if (currMode != newAudioMode) {
    + /// M: PTS TC_HF_ICA_BV_05_I {@
    + if ((currMode == AudioManager.MODE_IN_CALL) &&
    + (mInBandRingtone == HeadsetClientHalConstants.IN_BAND_RING_NOT_PROVIDED)) {
    + disconnectAudioNative(getByteAddress(mCurrentDevice));
    + mAudioManager.abandonAudioFocusForCall();
    + }
    + /// @}
    + // request audio focus before setting the new mode
    + mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
    + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
    + Log.d(TAG, "setAudioMode Setting audio mode from "
    + + currMode + " to " + newAudioMode);
    + mAudioManager.setMode(newAudioMode);
    + }
    +
    break;
    case EVENT_TYPE_CGMI:
    Log.d(TAG, "cgmi:" + event.valueString);
    @@ -2149,6 +2218,18 @@ final class HeadsetClientStateMachine extends StateMachine {
    case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
    Log.d(TAG, "Connected disconnects.");
    // AG disconnects
    +
    + if (mAudioManager.getMode() != AudioManager.MODE_NORMAL) {
    + mAudioManager.setMode(AudioManager.MODE_NORMAL);
    + Log.d(TAG, "abandonAudioFocus");
    + // abandon audio focus after the mode has been set back to normal
    + mAudioManager.abandonAudioFocusForCall();
    + }
    + Log.d(TAG,"hfp_enable=false");
    + mAudioManager.setParameters("hfp_enable=false");
    +
    if (mCurrentDevice.equals(device)) {
    broadcastConnectionState(mCurrentDevice,
    BluetoothProfile.STATE_DISCONNECTED,
    @@ -2196,6 +2277,11 @@ final class HeadsetClientStateMachine extends StateMachine {
    
    // We need to set the volume after switching into HFP mode as some Audio HALs
    // reset the volume to a known-default on mode switch.
    +
    + /*
    final int amVol =
    mAudioManager.getStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO);
    final int hfVol = amToHfVol(amVol);
    @@ -2213,6 +2299,41 @@ final class HeadsetClientStateMachine extends StateMachine {
    Log.d(TAG, "hf_volume " + hfVol);
    mAudioManager.setParameters("hfp_enable=true");
    mAudioManager.setParameters("hfp_volume=" + hfVol);
    + */
    + //New code
    + BluetoothHeadsetClientCall c = getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING);
    + if (mInBandRingtone == HeadsetClientHalConstants.IN_BAND_RING_PROVIDED && c != null) {
    + // Purpose: To workaroud audio driver limitation
    + Log.d(TAG, "Skip In-Band ringtone");
    + } else {
    + int newAudioMode = AudioManager.MODE_IN_CALL;
    + int currMode = mAudioManager.getMode();
    + if (currMode != newAudioMode) {
    + // request audio focus before setting the new mode
    + mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
    + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
    + Log.d(TAG, "setAudioMode Setting audio mode from "
    + + currMode + " to " + newAudioMode);
    + mAudioManager.setMode(newAudioMode);
    + }
    + Log.d(TAG,"hfp_enable=true");
    + Log.d(TAG,"mAudioWbs is " + mAudioWbs);
    + if (mAudioWbs) {
    + Log.d(TAG,"Setting sampling rate as 16000");
    + mAudioManager.setParameters("hfp_set_sampling_rate=16000");
    + }
    + else {
    + Log.d(TAG,"Setting sampling rate as 8000");
    + mAudioManager.setParameters("hfp_set_sampling_rate=8000");
    + }
    + mAudioManager.setParameters("hfp_enable=true");
    + }
    + broadcastAudioState(device, BluetoothHeadsetClient.STATE_AUDIO_CONNECTED,
    + BluetoothHeadsetClient.STATE_AUDIO_CONNECTING);
    +
    +
    transitionTo(mAudioOn);
    break;
    case HeadsetClientHalConstants.AUDIO_STATE_CONNECTING:
    @@ -2339,6 +2460,18 @@ final class HeadsetClientStateMachine extends StateMachine {
    if (mCurrentDevice.equals(device)) {
    processAudioEvent(HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED,
    device);
    +
    + if (mAudioManager.getMode() != AudioManager.MODE_NORMAL) {
    + mAudioManager.setMode(AudioManager.MODE_NORMAL);
    + Log.d(TAG, "abandonAudioFocus");
    + // abandon audio focus after the mode has been set back to normal
    + mAudioManager.abandonAudioFocusForCall();
    + }
    + Log.d(TAG,"hfp_enable=false");
    + mAudioManager.setParameters("hfp_enable=false");
    
    broadcastConnectionState(mCurrentDevice,
    BluetoothProfile.STATE_DISCONNECTED,
    BluetoothProfile.STATE_CONNECTED);
    @@ -2371,6 +2504,20 @@ final class HeadsetClientStateMachine extends StateMachine {
    // (such as Telecom) and hence this will still keep the call around, there
    // is not much we can do here since dropping the call without user consent
    // even if the audio connection snapped may not be a good idea.
    
    + BluetoothHeadsetClientCall c = getCall(BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
    + if (c==null) {
    + if (mAudioManager.getMode() != AudioManager.MODE_NORMAL) {
    + mAudioManager.setMode(AudioManager.MODE_NORMAL);
    + Log.d(TAG, "abandonAudioFocus");
    + // abandon audio focus after the mode has been set back to normal
    + mAudioManager.abandonAudioFocusForCall();
    + }
    + }
    Log.d(TAG,"hfp_enable=false");
    mAudioManager.setParameters("hfp_enable=false");
    broadcastAudioState(device,
    

     

    二,关于蓝牙音乐和导航语音播报的时候不能混音输出的问题。

    原因是航界面的语音播报是持续的并且是连续的,而7.1的蓝牙在收到焦点切换的时候会进行蓝牙音乐的释放与焦点转换,这个问题的处理方式是对音频焦点切换的时候不做处理,让蓝牙音乐继续之前的播放,这样就可以在蓝牙音乐播放的同事,导航的音频也可以持续的输出,形成混音同时输出

    patch如下:

    diff --git a/apps/Bluetooth/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamingStateMachine.java b/apps/Bluetooth/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamingStateMachine.java
    index b8b11d1..4c36729 100644
    --- a/apps/Bluetooth/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamingStateMachine.java
    +++ b/apps/Bluetooth/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamingStateMachine.java
    @@ -365,7 +365,8 @@ final class A2dpSinkStreamingStateMachine extends StateMachine {
    if (newAudioFocus == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
    
    - sendAvrcpPause();
    //sendAvrcpPause();
    - transitionTo(mFTransient);
    + //transitionTo(mFTransient);
    }
    if (newAudioFocus == AudioManager.AUDIOFOCUS_LOSS) {
    sendAvrcpPause();
    @@ -417,7 +418,8 @@ final class A2dpSinkStreamingStateMachine extends StateMachine {
    if (newAudioFocus == AudioManager.AUDIOFOCUS_GAIN)
    
    - sendAvrcpPlay();
    //sendAvrcpPlay();
    - transitionTo(mFGranted);
    + //transitionTo(mFGranted);
    if (newAudioFocus == AudioManager.AUDIOFOCUS_LOSS) {
    abandonAudioFocus();
    

    三,蓝牙音乐和导航播报连续短暂卡顿问题

    这个问题是属于蓝牙音乐和导航播报走的是不同的音频流,音频流流频繁切换导致方法是使用别的音频流通道

    diff --git a/bt/btif/src/btif_avrcp_audio_track.cpp b/bt/btif/src/btif_avrcp_audio_track.cpp
    index 89d2a5f..6e6377e 100644
    --- a/bt/btif/src/btif_avrcp_audio_track.cpp
    +++ b/bt/btif/src/btif_avrcp_audio_track.cpp
    @@ -43,7 +43,7 @@
    sp<android::AudioTrack> track =
    new android::AudioTrack(AUDIO_STREAM_MUSIC, trackFreq, AUDIO_FORMAT_PCM_16_BIT,
    channelType, (size_t) 0 /*frameCount*/,
    - (audio_output_flags_t)AUDIO_OUTPUT_FLAG_FAST,
    + (audio_output_flags_t)AUDIO_OUTPUT_FLAG_DEEP_BUFFER,
    NULL /*callback_t*/, NULL /*void* user*/, 0 /*notificationFrames*/,
    AUDIO_SESSION_ALLOCATE, android::AudioTrack::TRANSFER_SYNC);
    assert(track != NULL);

     

    展开全文
  • RDA5856ESE是一个高性能高集成多媒体蓝牙4.2芯片,整合所有的基本电子元件,包括基带,蓝牙收发器,电源管理集成在一个系统芯片,专业应用于蓝牙音乐和音频应用程序。应用程序: Bluetooth 扬声器、 Bluetooth 音乐盒、...
  • 需求:手机端音乐暂停和播放状态从服务端告诉客户端、设备端实现暂停、播放、上一首、下一首等功能 代码路径: packages/apps/Bluetooth/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java ...

    需求:手机端音乐暂停和播放状态从服务端告诉客户端、设备端实现暂停、播放、上一首、下一首等功能
    代码路径:
    packages/apps/Bluetooth/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
    packages/apps/Bluetooth/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
    packages/apps/Bluetooth/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
    packages/apps/Bluetooth/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
    packages/apps/Bluetooth/jni/com_android_bluetooth_avrcp_controller.cpp
    一、蓝牙音乐播放状态
    1、在AvrcpControllerService.java文件中onPlayStatusChanged()方法就是音乐播放状态改变,该方法是由JNI层中com_android_bluetooth_avrcp_controller.cpp中调用java层的

    private synchronized void onPlayStatusChanged(byte[] address, byte playStatus) {
        if (DBG) {
            Log.d(TAG, "onPlayStatusChanged " + playStatus);
        }
        int playbackState = PlaybackState.STATE_NONE;
        switch (playStatus) {
            case JNI_PLAY_STATUS_STOPPED:
                playbackState = PlaybackState.STATE_STOPPED;
                break;
            case JNI_PLAY_STATUS_PLAYING:
                playbackState = PlaybackState.STATE_PLAYING;
                break;
            case JNI_PLAY_STATUS_PAUSED:
                playbackState = PlaybackState.STATE_PAUSED;
                break;
            case JNI_PLAY_STATUS_FWD_SEEK:
                playbackState = PlaybackState.STATE_FAST_FORWARDING;
                break;
            case JNI_PLAY_STATUS_REV_SEEK:
                playbackState = PlaybackState.STATE_REWINDING;
                break;
            default:
                playbackState = PlaybackState.STATE_NONE;
        }
        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
        if (stateMachine != null) {
            stateMachine.sendMessage(
                    AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
        }
    }
    

    2、在AvrcpControllerStateMachine.java文件中Connected类中MESSAGE_PROCESS_PLAY_STATUS_CHANGED消息就是处理播放状态的,然后通过广播方式把该状态发送出去

    case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
        if (SystemProperties.get("persist.ivi.feature", "0").equals("1")) {
            A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
            Log.d(TAG, "the state: " + msg.arg1);
            if (a2dpSinkService != null && msg.arg1 != preAudioStatus) {
                preAudioStatus = msg.arg1;
                Log.d(TAG, "preAudioStatus: " + msg.arg1);
                if (msg.arg1 == PlaybackState.STATE_PLAYING) {
                    //播放
                        a2dpSinkService.informPlayState(mDevice, true);
                } else if (msg.arg1 == PlaybackState.STATE_PAUSED) {
                    //暂停
                        a2dpSinkService.informPlayState(mDevice, false);
                }
            }
        }
        Intent intent = new Intent("zqc.bluetooth.PLAY_STATUS_CHANGED");
    intent.putExtra("zqc.bluetooth.PLAYBACK", msg.arg1);
    mService.sendBroadcast(intent);
        mAddressedPlayer.setPlayStatus(msg.arg1);
        BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
        if (mAddressedPlayer.getPlaybackState().getState()
                == PlaybackState.STATE_PLAYING
                && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE
                && !shouldRequestFocus()) {
            sendMessage(MSG_AVRCP_PASSTHRU,
                    AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
        }
        return true;
    

    二、蓝牙音乐中怎么获取音乐信息
    1、在AvrcpControllerService.java文件中onTrackChanged()方法中是获取JNI层上报的音乐信息,该方法是由JNI层中com_android_bluetooth_avrcp_controller.cpp中调用java层的

    private synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
            String[] attribVals) {
        if (DBG) {
            Log.d(TAG, "onTrackChanged");
        }
    
        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
        //该方法就是把蓝牙音乐的信息传递给java层
        getElementAttrRsp(attributes,attribVals,numAttributes);
        if (stateMachine != null) {
            stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED,
                    TrackInfo.getMetadata(attributes, attribVals));
        }
    }
    

    2、在AvrcpControllerService.java文件中getElementAttrRsp()中,然后通过该方法中通过广播告诉出去

    private void getElementAttrRsp(int[] attr_id,String[] textArray,byte num_attr){
        String artist = null;
        String trackTitle = null;
        String album = null;
        for (int i = 0; i < num_attr; i++){
            switch (attr_id[i]) {
                case JNI_MEDIA_ATTR_ID_TITLE:
                    trackTitle = textArray[i];
                    if (trackTitle == null){
                        trackTitle = "Unknown";
                    }
                    break;
                case JNI_MEDIA_ATTR_ID_ARTIST:
                    artist = textArray[i];
                    if (artist == null){
                        artist = "Unknown";
                    }
                    break;
                case JNI_MEDIA_ATTR_ID_ALBUM:
                    album = textArray[i];
                    if (album == null){
                        album = "Unknown";
                    }
                    break;
            }
        }
        Intent intent = new Intent("com.android.getelementattrrsp");
        intent.putExtra("artist", artist);
        intent.putExtra("trackTitle",trackTitle);
        intent.putExtra("album",album);
        Log.d(TAG,"getElementAttrRsp,artist: " + artist + ",trackTitle: " + trackTitle + ",album: " + album);
        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
    }
    

    三、设备蓝牙音乐中上一首、下一首、暂停功能控制手机端
    1、在AvrcpControllerService.java文件中sendPassThroughCmd()方法来控制暂停、播放、上一首、下一首等功能

    public synchronized void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
        Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState);
        if (device == null) {
            Log.e(TAG, "sendPassThroughCmd Device is null");
            return;
        }
    
        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
        if (stateMachine != null) {
            stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_SEND_PASS_THROUGH_CMD,
                keyCode, keyState, device);
        }
    }
    

    2、在AvrcpControllerStateMachine.java文件中MESSAGE_SEND_PASS_THROUGH_CMD变量中通过JNI层来实现功能

    case MESSAGE_SEND_PASS_THROUGH_CMD:
        BluetoothDevice device = (BluetoothDevice) msg.obj;
        mService.sendPassThroughCommandNative(Utils.getByteAddress(device), msg.arg1,
                        msg.arg2);
        return true;
    
    展开全文
  • 熟悉蓝牙音乐播放流程的智能硬件开发工程师都了解蓝牙音乐AVDTP的控制流与音频流以及AVRCP是如何作用的,以如下的交互图简单介绍下: AVDTP连接阶段分为四步: 1、发现对端支持的编码方式有哪几种 2、获取每种编码...

    当今这个音视频无处不在的时代,音频跟踪会话ID(AudioTrack Session ID)是个很重要的参数,可以用来实现音频相关的一些特效。接下来的内容我们就探究如何在安卓蓝牙系统中获取该id。

    熟悉蓝牙音乐播放流程的智能硬件开发工程师都了解蓝牙音乐AVDTP的控制流与音频流以及AVRCP是如何作用的,以如下的交互图简单介绍下:
    在这里插入图片描述
    AVDTP连接阶段分为四步:

    1、发现对端支持的编码方式有哪几种

    2、获取每种编码方式的参数

    3、双方协商确定一个最优的编码方式

    4、建立该编码方式的AVDTP连接
    在这里插入图片描述
    音频播放过程中只要创建的audiotrack不变则对应的session id就会保持不变。播放蓝牙音乐也是这样。那蓝牙音乐对应的audiotracksession id从哪儿获取呢?

    创建audiotrack都会生成对应的session id,那问题就简单多了,蓝牙音乐播放过程中audiotrack在哪儿被创建的?

    蓝牙音乐播放分为source、sink两端,source一般为音频流输出端,sink接收音频流方。这篇文章主要是已sink端来讲解蓝牙音频播放流程。Sink方(蓝牙耳机、音响、车载等)的蓝牙芯片接收到音频流后通过HCI送到蓝牙协议栈,在协议栈中直接通过audio track送入安卓音频系统播放音乐。

    结合蓝牙协议栈的代码可以快速找到audio track的创建处为:BtifAvrcpAudioTrackCreate(),通过spandroid::AudioTracktrack可以实现session id的获取。

    那如何将该id送到application层呢?这块就需要我们了解安卓蓝牙系统中媒体音频的架构,从而添加接口来获取session id。

    安卓系统中关于媒体音频的架构类似于整个蓝牙系统,见下图:
    在这里插入图片描述
    根据上面的系统架构图,则获取audiotrack session id的接口主要和framework、service、JNI以及Bluedroid中btif都有关系。大概的时序图如下:
    在这里插入图片描述

    展开全文
  • 蓝牙音乐之A2DP音频流

    2020-07-17 09:56:03
    蓝牙音乐之A2DP音频流 A2DP音频流的建立已经在《蓝牙音乐之A2DP》中做了简单分享,本篇我们主要来说说音频流中两个重要的过程:开始、暂停 由于音频流表示一种单向媒体数据,那么音频流两端的设备就显式地承担起 ...
  • Android蓝牙音乐获取歌曲信息

    千次阅读 2017-10-09 09:35:00
     由于我在蓝牙开发方面没有多少经验,如果只是获取一下蓝牙设备名称和连接状态那么前面的那篇文章就已经足够了,接下来的内容是转自一个在蓝牙音乐方面颇有经验的开发者的博客,他的这篇文章对我帮助很大. ...
  • 蓝牙协议的几种profile: btservice: 统一管理,控制其他服务。 a2dp: 和蓝牙耳机,音频有关,比如听歌等。 avrcp: 音频/视频通过连接的蓝牙控制,比如放歌时控制暂停等。 gatt:低功耗BLE有关,比如蓝牙按键。 hdp: 蓝牙...
  • iPhone从CarLife音乐切换到蓝牙音乐音量变小现象分析总结 现象 iPhone7 Plus,IOS版本10.2.1,播放蓝牙音乐,然后切换到CarLife音乐,然后再切换到蓝牙音乐,期间没有改变音量,但是蓝牙音乐的声音对比切换前变小了 ...
  • 最近发现公司自研的智能车载设备上存在一个问题:手机通过蓝牙连接车机,在手机上播放音乐车机上发声然后跟车机上的导航语音播报一起混音播出的时候会出现卡顿的现象;但是在车机上播放音乐和导航混音就正常。另外...
  • 蓝牙音乐之绝对音量

    千次阅读 2020-10-13 16:55:43
    蓝牙音乐之绝对音量 蓝牙音乐播放中总是避免不了音量调节的操作,生活中最常见的场景就是手机连接蓝牙耳机或音箱播放音乐,通过调节手机上的多媒体音量达到蓝牙音乐音量调节的目的。这些功能是如何具体实现的,使用...
  • 蓝牙音乐播放进度一般由蓝牙模块主动发送,车机端通过回调来更新,实际开发中,发现在安富方案,播放进度回调会出现不及时或者没有实时回调的情况 所以需要我们调用主动获取进度的api,当笔记记录一下 private Ui...
  • QQ音乐车机模式中蓝牙音乐无效

    千次阅读 2019-12-16 17:00:29
    QQ音乐车机模式中蓝牙音乐无效现象分析总结 现象 车机与手机QQ音乐以车机模式连接中,从QQ音乐切换到蓝牙音乐蓝牙音乐操作无效 分析 这种问题十分坑,不过自觉,直觉告诉我要先看下是不是手机的问题,分析...
  • Android 蓝牙音乐播放控制的接口实现

    千次阅读 2019-11-09 14:50:30
     * 可以在收到BluetoothDevice.ACTION_ACL_DISCONNECTED 蓝牙断开后调用该函数  */  private void disconnectMediaBrowser() {  if(null != mMediaBrowser) {  mMediaBrowser.disconnect();  ...
  • 车载蓝牙音乐播放的时候(连接手机,在手机端播放,车机端蓝牙的角色是sink).mSortedAudioPlaybackClientUids的size为0.我擦,为什么是这样!!!let me find out why so it is! mSortedAudioPlaybackClientUids是在...
  • 蓝牙音乐音质损耗

    万次阅读 2016-06-14 14:15:26
    蓝牙设备为何音质差 最近这段时间Wi-Fi音箱可谓是非常热门的一个话题,也许平时不怎么关注音箱耳机的一些网友都有所耳闻了,就现在的情况而言,很多Wi-Fi音箱在宣传时都会和蓝牙产品进行音质上的对比,称...
  • 蓝牙音乐之AVRCP在安卓系统中的实现

    千次阅读 热门讨论 2020-08-27 12:19:55
    蓝牙音乐之AVRCP在安卓系统中的实现 从《蓝牙音乐之AVRCP》协议分析中可知,音视频远端控制协议被分成两部分:CT + TG,因此安卓源码也相应的分为 avrcp + avrcpcontroller 两部分。一般情况下avrcp对应TG,配置于...
  • 提出一种基于蓝牙技术的音乐喷泉控制系统的设计方案,通过蓝牙模块实现音乐喷泉和手机App 的通信,采用ADC0832将手机传输的音乐从模拟量转换为数字量,用单片机串口将音乐数据发送给蓝牙模块,从而完成数据的无线...
  • 如题,如何在播放端,获取到当前正在通过蓝牙播放的音乐的信息?比如歌曲名,作者之类的。暂时只能对音乐进行一些控制。希望能拿到音乐的信息。 简单说一下,a2dp能够对音乐进行一些指令操作,比如上一首下一首,...
  • 熟悉蓝牙音乐播放流程的同学都了解蓝牙音乐AVDTP的控制流与音频流以及AVRCP是如何作用的,以如下的交互图简单介绍下: AVDTP连接阶段分为四步: 1、 发现对端支持的编码方式有哪几种 2、 获取...
  • 1.高通MSM8953和SDM450蓝牙电话(SCO)和蓝牙音乐(A2DP),这两个片子硬件设计是一样 <1>.蓝牙电话通路(SCO) 蓝牙接收:BT--->ADSP 注意:ADSP没有USB接口,所以不支持USB Headset,如果需要支持,需要修改BT...
  • 行业资料-电子功用-一体式的自动充电式蓝牙音乐花洒
  • 蓝牙音乐焦点管理

    2019-09-11 14:39:14
    1、手机连接车机蓝牙,车机蓝牙音乐A2dpSink管理流程 A2dpSink在收到onPrepare或者请求播放音乐时,判断当前是否已获取到焦点,如果没有获取到焦点,则申请焦点。如果申请成功则更新avrcp状态信息,通知bluedroid...
  • 蓝牙音乐之AVRCP

    千次阅读 2020-08-25 12:55:51
    蓝牙音乐之AVRCP 蓝牙音乐实实在在地改变了我们听音乐的方式,尤其是iphone推出真无线耳机后,这一趋势更加明显。蓝牙音乐中不单单涉及到A2DP,对A2DP还不了解的小伙伴可以查看《蓝牙音乐之A2DP》,还有音乐远端...
  • 行业分类-电子电器-一种基于穿戴设备的离线蓝牙音乐手环.zip
  • 用于ESP32的简单Arduino蓝牙音乐接收器和发送器 ESP32提供了一个蓝牙A2DP API,可从您的手机接收声音数据,并通过回调方法使之可用。 输出是从SBC格式解码的PCM数据流。 该文档可在找到。 I2S是用于将数字音频设备...
  • 蓝牙,Airplay,Spotify Connect和UPnP :使用蓝牙或UPnP从您喜欢的音乐服务或直接从智能手机/计算机中流式传输音频。 多房间同步播放:在您所在位置的多个设备上播放完美同步的音频。 扩展的DAC支持:使用我们...
  • 行业分类-电器装置-一种具有报警功能的炫彩蓝牙音乐强光手电筒.zip

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 16,538
精华内容 6,615
关键字:

蓝牙音乐