RK3568 Android 11 蓝牙BluetoothA2dpSink 获取用于生成频谱的PCM

在这里插入图片描述

Android 中的 A2DP Sink

A2DP Sink 在 Android 系统中主要用于 接收 其他蓝牙设备(如手机、平板、电脑等)发送过来的 高质量的立体声音频。简单来说,它让你的 Android 设备可以充当一个 蓝牙音箱耳机 的角色。

核心功能:

  • 接收音频流: 通过蓝牙协议接收来自其他设备的音频数据。
  • 解码音频: 将接收到的音频数据解码成可播放的音频格式。
  • 播放音频: 通过设备的扬声器或耳机输出解码后的音频。

应用场景:

  • 无线音箱: 将 Android 设备连接到蓝牙音箱,实现无线音乐播放。
  • 车载蓝牙: 将手机连接到车载蓝牙系统,通过车载音响播放音乐。
  • 蓝牙耳机: 将 Android 设备连接到蓝牙耳机,进行通话或听音乐。

技术实现:

  • BluetoothA2dpSink: Android 提供了 BluetoothA2dpSink 类来实现 A2DP Sink 功能。开发者可以通过这个类来管理 A2DP 连接、控制音频播放等。
  • 蓝牙配置文件: A2DP(Advanced Audio Distribution Profile)是一种蓝牙配置文件,专门用于高质量立体声音频的无线传输。

如何获取音频数据并生成音频频谱?

什么是音乐频谱?
音乐频谱是声音频率的分布图。声音是由不同频率的声波组成的,这些声波的振幅(强度)不同,就形成了不同的音色。频谱图就是将这些频率和振幅的关系用图形表示出来。
在这里插入图片描述

频谱图的组成
  • 横轴: 表示频率,通常以赫兹(Hz)为单位。频率越高,音调越高。
  • 纵轴: 表示振幅,也就是声音的强度。振幅越大,声音越响。
  • 颜色或灰度: 表示不同频率的振幅大小。颜色越深或灰度越高,表示该频率的振幅越大。
频谱图的种类
  • 线性频谱图: 频率轴按线性比例分布,适用于分析整个音频频段。
  • 对数频谱图: 频率轴按对数比例分布,更适合显示低频部分的细节,常用于音频分析。
  • 时频图: 显示声音频率随时间的变化情况,可以直观地看到声音的动态变化。
    在这里插入图片描述
总的来说

音乐频谱是了解声音的重要工具,它不仅能帮助我们更好地理解声音的本质,还能在音乐创作、音频处理等领域发挥重要作用。


在蓝牙音箱的模式下, 如何生成音频频谱?

    在打上RK提供的A2dpSink补丁后, 手机等设备可以通过蓝牙连接播放音乐, RK3568充当蓝牙音箱的角色. 在这种状态下, 系统播放音频并不是采用android上层的MediaPlayerAudioTrack, 所以无法采用常规的方式来生成, 若需要获取播放器的音频频谱, 首先, 需要获得音频的PCM数据.

在蓝牙音箱模式下, 音频的播放器的位置处于android 源码的 system目录下

system/bt/btif/src/btif_avrcp_audio_track.cc

/** Copyright 2015 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/#define LOG_NDEBUG 1
#define LOG_TAG "bt_btif_avrcp_audio_track"#include "btif_avrcp_audio_track.h"#include <aaudio/AAudio.h>
#include <base/logging.h>
#include <utils/StrongPointer.h>#include "bt_target.h"
#include "osi/include/log.h"using namespace android;typedef struct {AAudioStream* stream;int bitsPerSample;int channelCount;float* buffer;size_t bufferLength;
} BtifAvrcpAudioTrack;#if (DUMP_PCM_DATA == TRUE)
FILE* outputPcmSampleFile;
char outputFilename[50] = "/data/misc/bluedroid/output_sample.pcm";
#endifvoid* BtifAvrcpAudioTrackCreate(int trackFreq, int bitsPerSample,int channelCount) {LOG_VERBOSE(LOG_TAG, "%s Track.cpp: btCreateTrack freq %d bps %d channel %d ",__func__, trackFreq, bitsPerSample, channelCount);AAudioStreamBuilder* builder;AAudioStream* stream;aaudio_result_t result = AAudio_createStreamBuilder(&builder);AAudioStreamBuilder_setSampleRate(builder, trackFreq);AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_FLOAT);AAudioStreamBuilder_setChannelCount(builder, channelCount);AAudioStreamBuilder_setSessionId(builder, AAUDIO_SESSION_ID_ALLOCATE);AAudioStreamBuilder_setPerformanceMode(builder,AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);result = AAudioStreamBuilder_openStream(builder, &stream);CHECK(result == AAUDIO_OK);AAudioStreamBuilder_delete(builder);BtifAvrcpAudioTrack* trackHolder = new BtifAvrcpAudioTrack;CHECK(trackHolder != NULL);trackHolder->stream = stream;trackHolder->bitsPerSample = bitsPerSample;trackHolder->channelCount = channelCount;trackHolder->bufferLength =trackHolder->channelCount * AAudioStream_getBufferSizeInFrames(stream);trackHolder->buffer = new float[trackHolder->bufferLength]();#if (DUMP_PCM_DATA == TRUE)outputPcmSampleFile = fopen(outputFilename, "ab");
#endifreturn (void*)trackHolder;
}void BtifAvrcpAudioTrackStart(void* handle) {if (handle == NULL) {LOG_ERROR(LOG_TAG, "%s: handle is null!", __func__);return;}BtifAvrcpAudioTrack* trackHolder = static_cast<BtifAvrcpAudioTrack*>(handle);CHECK(trackHolder != NULL);CHECK(trackHolder->stream != NULL);LOG_VERBOSE(LOG_TAG, "%s Track.cpp: btStartTrack", __func__);AAudioStream_requestStart(trackHolder->stream);
}void BtifAvrcpAudioTrackStop(void* handle) {if (handle == NULL) {LOG_DEBUG(LOG_TAG, "%s handle is null.", __func__);return;}BtifAvrcpAudioTrack* trackHolder = static_cast<BtifAvrcpAudioTrack*>(handle);if (trackHolder != NULL && trackHolder->stream != NULL) {LOG_VERBOSE(LOG_TAG, "%s Track.cpp: btStartTrack", __func__);AAudioStream_requestStop(trackHolder->stream);}
}void BtifAvrcpAudioTrackDelete(void* handle) {if (handle == NULL) {LOG_DEBUG(LOG_TAG, "%s handle is null.", __func__);return;}BtifAvrcpAudioTrack* trackHolder = static_cast<BtifAvrcpAudioTrack*>(handle);if (trackHolder != NULL && trackHolder->stream != NULL) {LOG_VERBOSE(LOG_TAG, "%s Track.cpp: btStartTrack", __func__);AAudioStream_close(trackHolder->stream);delete trackHolder->buffer;delete trackHolder;}#if (DUMP_PCM_DATA == TRUE)if (outputPcmSampleFile) {fclose(outputPcmSampleFile);}outputPcmSampleFile = NULL;
#endif
}void BtifAvrcpAudioTrackPause(void* handle) {if (handle == NULL) {LOG_DEBUG(LOG_TAG, "%s handle is null.", __func__);return;}BtifAvrcpAudioTrack* trackHolder = static_cast<BtifAvrcpAudioTrack*>(handle);if (trackHolder != NULL && trackHolder->stream != NULL) {LOG_VERBOSE(LOG_TAG, "%s Track.cpp: btPauseTrack", __func__);AAudioStream_requestPause(trackHolder->stream);AAudioStream_requestFlush(trackHolder->stream);}
}void BtifAvrcpSetAudioTrackGain(void* handle, float gain) {if (handle == NULL) {LOG_DEBUG(LOG_TAG, "%s handle is null.", __func__);return;}// Does nothing right now
}constexpr float kScaleQ15ToFloat = 1.0f / 32768.0f;
constexpr float kScaleQ23ToFloat = 1.0f / 8388608.0f;
constexpr float kScaleQ31ToFloat = 1.0f / 2147483648.0f;static size_t sampleSizeFor(BtifAvrcpAudioTrack* trackHolder) {return trackHolder->bitsPerSample / 8;
}static size_t transcodeQ15ToFloat(uint8_t* buffer, size_t length,BtifAvrcpAudioTrack* trackHolder) {size_t sampleSize = sampleSizeFor(trackHolder);size_t i = 0;for (; i <= length / sampleSize; i++) {trackHolder->buffer[i] = ((int16_t*)buffer)[i] * kScaleQ15ToFloat;}return i * sampleSize;
}static size_t transcodeQ23ToFloat(uint8_t* buffer, size_t length,BtifAvrcpAudioTrack* trackHolder) {size_t sampleSize = sampleSizeFor(trackHolder);size_t i = 0;for (; i <= length / sampleSize; i++) {size_t offset = i * sampleSize;int32_t sample = *((int32_t*)(buffer + offset - 1)) & 0x00FFFFFF;trackHolder->buffer[i] = sample * kScaleQ23ToFloat;}return i * sampleSize;
}static size_t transcodeQ31ToFloat(uint8_t* buffer, size_t length,BtifAvrcpAudioTrack* trackHolder) {size_t sampleSize = sampleSizeFor(trackHolder);size_t i = 0;for (; i <= length / sampleSize; i++) {trackHolder->buffer[i] = ((int32_t*)buffer)[i] * kScaleQ31ToFloat;}return i * sampleSize;
}static size_t transcodeToPcmFloat(uint8_t* buffer, size_t length,BtifAvrcpAudioTrack* trackHolder) {switch (trackHolder->bitsPerSample) {case 16:return transcodeQ15ToFloat(buffer, length, trackHolder);case 24:return transcodeQ23ToFloat(buffer, length, trackHolder);case 32:return transcodeQ31ToFloat(buffer, length, trackHolder);}return -1;
}constexpr int64_t kTimeoutNanos = 100 * 1000 * 1000;  // 100 msint BtifAvrcpAudioTrackWriteData(void* handle, void* audioBuffer,int bufferLength) {BtifAvrcpAudioTrack* trackHolder = static_cast<BtifAvrcpAudioTrack*>(handle);CHECK(trackHolder != NULL);CHECK(trackHolder->stream != NULL);aaudio_result_t retval = -1;//return 0;
#if (DUMP_PCM_DATA == TRUE)if (outputPcmSampleFile) {fwrite((audioBuffer), 1, (size_t)bufferLength, outputPcmSampleFile);}
#endifsize_t sampleSize = sampleSizeFor(trackHolder);int transcodedCount = 0;do {transcodedCount +=transcodeToPcmFloat(((uint8_t*)audioBuffer) + transcodedCount,bufferLength - transcodedCount, trackHolder);retval = AAudioStream_write(trackHolder->stream, trackHolder->buffer,transcodedCount / (sampleSize * trackHolder->channelCount),kTimeoutNanos);LOG_VERBOSE(LOG_TAG, "%s Track.cpp: btWriteData len = %d ret = %d",__func__, bufferLength, retval);} while (transcodedCount < bufferLength);return transcodedCount;
}

BtifAvrcpAudioTrackWriteData 函数中可以把PCM数据取出来用, 可以打开 DUMP_PCM_DATA 把蓝牙音频播放的PCM内容保存到本地文件char outputFilename[50] = "/data/misc/bluedroid/output_sample.pcm";中, 把文件拿出来用工具打包成WAV格式, 测试音频数据的正确性!

拿到PCM数据后, 通过算法, 便可以轻松实现音频频谱功能.

参考

  • Android 音频可视化:频谱特效的探索与实践
  • android获取和展示音乐的频谱
  • [RK3566-Android11] 关于 a2dpsink -蓝牙支持接收播放/无PIN码连接
  • Android 音频可视化

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

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

相关文章

【Java】SpringBoot 单体项目创建 与 整合 Mybatis-Plus

文章目录 前言1. 创建项目与整合MP1.1 IDEA创建SpringBoot项目1.2 SpringBoot整合Mybatis-Plus 2. 远程仓库2.1 创建远程仓库/本地仓库2.2 Add/Commit/Push/Pull 3. 总结与补充3.1 解决refusing to merge unrelated histories3.2 总结3.3 结语 参考资料 SpringBoot 单体项目创建…

Hadoop环境搭建

一、Linux环境准备 Linux命令查询https://www.linuxcool.com/ http://linux.51yip.com/ 安装Linux虚拟机 安装 sudo apt install open-vm-tools 安装 sudo apt install open-vm-tools-desktop &#xff08;可选&#xff09;换国内源 ​​ sudo apt update 更新软件列表&…

火焰传感器详解(STM32)

目录 一、介绍 二、传感器原理 1.原理图 2.引脚描述 三、程序设计 main.c文件 IR.h文件 IR.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 火焰传感器是一种常用于检测火焰或特定波长&#xff08;760nm-1100nm&#xff09;红外光的传感器。探测角度60左右&am…

Docker占用根目录/存储空间过多如何清理?

问题背景 使用df -h查看磁盘空间时发现根目录空间不多了&#xff0c;已使用96%&#xff0c;红色警告&#xff01;&#xff01;&#xff01; 于是使用df -h /* 一层一层定位&#xff0c;终于找到了一个大文件 9G多的文件夹&#xff0c;位置是&#xff1a; /var/lib/docker/o…

无线通信-WIFI通信

文章目录 1. 基础知识2. 工作模式3. AT指令4. 常用AT指令实例5. 连接原子云6. 使用usb转ttl模块测试ATK-MW8266D7. 使用STM32F103ZET6战舰开发板透传模式8. 使用STM32F103ZET6战舰板连接原子云 1. 基础知识 ATK-ESP-01 ATK-ESP-01模块支持标准的IEEE802.11b/g/n协议&#xff0c…

【Linux】文件魔法师:时间与日历的解密

欢迎来到 CILMY23 的博客 &#x1f3c6;本篇主题为&#xff1a;文件魔法师&#xff1a;时间与日历的解密 &#x1f3c6;个人主页&#xff1a;CILMY23-CSDN博客 &#x1f3c6;系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法 | 贪心算法 | Linux | 算法专题 | 代码…

【uniapp重大bug】uni-data-select的localdata改变,也会触发@change方法

bug描述 uni-data-select的下拉列表值localdata是动态获取的&#xff0c;且绑定了change方法&#xff0c;在页面加载后&#xff0c;请求localdata的列表数据&#xff0c;给localdata重新赋值&#xff0c;此时发现自动触发了change方法 当前uni版本&#xff1a;^2.0.2-30709202…

Axure RP10安装教程(Pro版)

下载链接 https://ga90eobypbb.feishu.cn/docx/UyzSd4q8SoXySjxtrcac4QnVn3f Axure RP 是一款专业的快速原型设计工具。它能帮助用户高效地创建网页和移动应用的线框图、流程图、原型和规格说明文档。拥有丰富的交互组件&#xff0c;可模拟各种复杂交互效果&#xff0c;如点击…

【千帆AppBuilder】使用Python调用基于官方的API创建图片故事的应用,一起体验下全代码模式下是怎样的效果

欢迎来到《小5讲堂》 这是《千帆》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 背景基本信息名称简介角色指令 能力扩展组件对话开场白推荐问 模型选…

98.SAP MII功能详解(12)Workbench-Transaction Logic(For Next Loop)

目录 1.Logic->For Next Loop 2.演示 配置对象 配置连接 for循环的整体演示 1.Logic->For Next Loop 此操作用于在预定义的次数内执行任务。每次迭代都会执行直接跟随For Next循环操作的所有操作&#xff0c;直到达到To限制。 若要在达到To属性限制之前停止&…

旅行追踪和行程规划工具AdventureLog

什么是 AdventureLog &#xff1f; AdventureLog 是一种记录您的旅行并与世界分享的简单方法。您可以在日志中添加照片、笔记等。跟踪您访问过的国家、探索去过的地区和地方。您还可以查看您的旅行统计数据和里程碑。AdventureLog 旨在成为您终极的旅行伴侣&#xff0c;帮助您记…

【LLM】文生视频相关开源数据集(VidGen、Panda、Cogvideox等)

note 总结了VidGen数据集、Panda-70m数据集、Openvid数据集、OpenVid-1M数据集、Cogvideox训练数据准备过程、ShareGPT4Video数据集等在一篇综述中还总结了评估指标包括&#xff1a;峰值信噪比&#xff08;PSNR&#xff09;、结构相似性指数&#xff08;SSIM&#xff09;、Inc…

matlab 将数组从左向右翻转

目录 一、概述1、算法概述2、主要函数二、代码示例1、翻转行向量2、翻转字符元胞数组3、翻转多维数组三、参考链接本文由CSDN点云侠翻译,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的抄袭狗。 一、概述 1、算法概述 将数组从左向右翻转 2、主要…

C++实现彩虹猫时空隧道特效(无害)

#include <Windows.h> // 如果不是在Visual Studio环境下运行的话W最好改小写。 using namespace std;int main() {for (int i 1; i < 10; i) {HDC hdc GetWindowDC(GetDesktopWindow());RECT rect;GetWindowRect(GetDesktopWindow(), &rect);StretchBlt(hdc, r…

[工具使用]ellisys

工具打开&#xff1a; 1.连接ellisys电源&#xff0c;ellisys Computer接口USB连接电脑&#xff0c;Logic接口与板子出信号的GPIO口连接 工具配置 1.点击"Configure" 2.在打开的Recording options中选择Wireless选项卡 2.选择Wired选项卡​ i.勾选Logic transit…

中秋佳节,悦动之选,精选热门骨传导耳机深度推荐

在这个金秋送爽、月圆人团圆的中秋佳节&#xff0c;我们不仅仅沉浸在月饼的香甜与家人的温馨之中&#xff0c;更渴望一份能够连接心与自然的独特礼物&#xff0c;让这份团聚的时光更加丰富多彩。在这个充满诗意的季节里&#xff0c;我满怀欣喜地向您推荐一款集科技、健康与时尚…

Maven的相关配置和使用

Maven的配置&#xff1a; Maven的配置和Java差不多&#xff0c;从镜像站下载相关的Maven版本压缩包&#xff0c;然后解压到自己的D盘&#xff0c;在进行系统变量的配置&#xff0c;新建变量Maven_HOME&#xff0c;然后值设置为Maven的地址&#xff0c;一定是点开文件后就能出现…

页面间对象传递的几种方法

页面间对象传递的几种方法 1. 使用request对象传递2. 使用session对象传递3. 使用application对象传递4. 使用cookie传递 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Web开发中&#xff0c;页面间的数据传递是一个常见的需求。本文将…

java 实现文本转音频

文章目录 一、前言二、实现流程2.1 下载jacob-1.18.zip2.2 拷贝jacob-1.18-x64.dll2.3 pom 添加依赖2.4 代码实现 一、前言 本文基于Windows自带的SAPI.SpVoice&#xff0c;通过java代码实现文本转语音的功能。 二、实现流程 2.1 下载jacob-1.18.zip 链接&#xff1a;https…

号称史上最强AI的Google Gemini,通过大语言模型的帮助如何实现智能交互?

导读&#xff1a; 本文旨在探索一条创新的路径&#xff0c;即通过利用Google的Gemini Flash而非广为人知的LangChain&#xff0c;来实现与CSV文件的智能交互。本文将构建一个简单的CSV解释器&#xff0c;利用大型语言模型&#xff08;LLM&#xff09;来生成代码并解析数据&…