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

news2024/9/22 1:16:06

在这里插入图片描述

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";
#endif

void* 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");
#endif
  return (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 ms

int 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);
  }
#endif

  size_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.coloradmin.cn/o/2082030.html

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

相关文章

开放式耳机哪个品牌实用?南卡、漫步者、小米 三款爆火单品横评

开放式耳机因为设计新颖&#xff0c;不塞进耳朵&#xff0c;受到了很多人的喜爱。但是&#xff0c;市面上一些开放式耳机音质和佩戴体验一般&#xff0c;质量也参差不齐&#xff0c;这让消费者陷入了选择困难。这些品质低下的产品既无法提供优秀的音频享受&#xff0c;长期佩戴…

midwayjs 框架使用 rabbitmq 消息延迟

插件rabbitmq_delayed_message_exchange是RabbitMQ官方提供的一种用于实现延迟消息的解决方案。该插件将交换机类型扩展至x-delayed-message&#xff0c;这种类型的交换机能够将消息暂时挂起&#xff0c;直到设定的延迟时间到达&#xff0c;才将消息投递到绑定的队列中。这一特…

vue3插件原理

概览 vue3的生态圈提供了许多方便的插件或者工具&#xff0c;比如pinia、vue-router和Element Plus等&#xff0c;使用插件的写法一般如下: const app createApp(App);app.use(pinia); app.use(router); app.use(ElementPlus, { locale }); // 第二个参数:{locale} 为传给插…

milvus多个Querynode,资源消耗都打在一个节点上

milvus 查询时的原理 当读取数据时&#xff0c;MsgStream对象在以下场景中创建&#xff1a; 在 Milvus 中&#xff0c;数据必须先加载后才能读取。当代理收到数据加载请求时&#xff0c;会将请求发送给查询协调器&#xff0c;查询协调器决定如何将分片分配到不同的查询节点。…

最长回文子串:动态规划推导

最长回文子串&#xff1a;结合图形推导动态规划 题目介绍 本题可以在力扣找到&#xff0c;题号为5。 给你一个字符串 s&#xff0c;找到 s 中最长的 回文子串。 示例 1&#xff1a; 输入&#xff1a;s “babad” 输出&#xff1a;“bab” 解释&#xff1a;“aba” 同样是符…

Composio:开源项目中的AI智能体任务执行利器

目录 一、引言二、Composio 简介三、Composio 的功能特性四、Composio 的应用场景五、Composio 的应用实践1、安装 Composio 核心库2、安装OpenAI3、添加 GitHub 集成4、初始化Composio工具集5、获取预配置的 GitHub 工具6、工具函数配置7、执行工具函数 六、结语 一、引言 在…

可定制化内容具体识别事物,多方位同时监管的智慧快消开源了

智慧快消视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本。国产化人工智能“…

Vue实现zip压缩下载

1&#xff0c;安装依赖npm //jszip是一个用于创建、读取和编辑.zip文件的JavaScript库 https://stuk.github.io/jszip/ npm install jszip https://www.npmjs.com/package/file-saver npm install file-saver 2&#xff0c;在所需的页面中引入对应包 import JSZip from &…

3.服务注册_服务发现

文章目录 1.服务注册_服务发现1.1服务注册概念及图解介绍2.2 CAP理论2.3 常见的注册中心(了解)2.4 Eureka组件介绍2.4.1.搭建注册中心2.4.2服务注册2.4.3服务发现 大家好&#xff0c;我是晓星航。今天为大家带来的是 服务注册_服务发现 相关的讲解&#xff01;&#x1f600; 1…

自然语言常见面试题及答案(41~60)

Reply&#xff1a;面试题 获取资料下载 文章目录 41. 谈谈在自然语言处理中&#xff0c;如何评估模型的性能&#xff1f;42. 什么是语言模型&#xff08;Language Model&#xff09;&#xff1f;它在自然语言处理中的作用是什么&#xff1f;43. 如何进行文本分类任务&#xff…

外卖点餐配送系统源码的模块化设计:快速开发与迭代的秘诀

在快速发展的外卖行业中&#xff0c;点餐配送系统的开发需要具备高效、可扩展、易维护的特点。模块化设计能够有效地解决这些问题&#xff0c;通过将系统功能分解为多个独立的模块&#xff0c;使得开发团队可以快速开发和迭代每个模块&#xff0c;减少耦合度&#xff0c;提高系…

SpringBoot-读取配置文件方式

前言 Spring Boot提供了多种灵活的方式来读取配置文件&#xff0c;以适应不同的开发和部署需求&#xff0c;SpringBoot启动的时候&#xff0c;读取配置文件的时候&#xff0c;首先获取的是file:/config/文件下的配置文件&#xff0c;也就是项目下config文件里面的配置文件&…

Leetcode 216.组合总和Ⅲ 回溯+剪枝 C++实现

Leetcode 216.组合总和Ⅲ 问题&#xff1a;找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字 1 到 9每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次&#xff0c;组合可以以任何顺序返回。 算…

【rancher镜像】修改rancher官方镜像仓库为私有仓库

背景 在使用rancher构建k8s时&#xff0c;由于中国区网络问题经常没法访问docker的官方仓库镜像docker.io&#xff0c;而rancher在构建k8s时&#xff0c;会默认从docker.io去下载镜像&#xff0c;由于网络原因&#xff0c;构建就会存在问题&#xff0c;镜像无法下载&#xff0…

Cxx primer-chap17-Specialized Library Facilities

tuple(元组)是一个模板&#xff0c;类似于pair&#xff0c;但是支持多个member&#xff0c;其主要用于将多个数据合并成一个对象&#xff1a;不像pair访问成员是固定的&#xff08;first/second&#xff09;&#xff0c;访问tuple的成员需要使用函数模板get:bitset类比位运算方…

PDF秒变Word,你的文档编辑从此开挂!

在现代办公中&#xff0c;PDF和Word是我们最常接触的两种文件格式。PDF因其良好的兼容性和固定的格式而广受欢迎&#xff0c;但在编辑时却常常让人感到束手无策。而Word则因其强大的编辑功能成为文档处理的首选。 那么&#xff0c;如何将PDF转化为Word&#xff0c;让文档编辑更…

Linux多线程——线程的概念和控制

文章目录 线程的概念进程和线程对比 线程的控制创建线程与分配任务线程终止线程等待线程分离 pthread线程库 线程的概念 线程是我们经常听到的一个概念&#xff0c;他和进程有什么关系呢 从操作系统课本里我们可能听说过&#xff0c;线程是一个微缩版的进程&#xff0c;他拥有…

vue将二维码做成名片,并且生成图片保存

效果图 1. 安装html2canvas 首先&#xff0c;你需要在你的Vue项目中安装html2canvas。你可以通过npm或yarn来安装它&#xff1a; npm install html2canvas # 或者 yarn add html2canvas2.组件形式 2.1 创建组件 在你的Vue项目中&#xff0c;创建一个新的Vue组件&#x…

YOLO-World: Real-Time Open-Vocabulary Object Detection:实时开放词汇对象检测

YOLO系列探测器已成为高效实用的工具。然而&#xff0c;它们对预定义和训练的对象类别的依赖限制了它们在开放场景中的适用性。针对这一限制&#xff0c;我们引入了YOLO-World&#xff0c;这是一种创新方法&#xff0c;通过视觉语言建模和大规模数据集的预训练&#xff0c;增强…

深度学习入门-10

基于小土堆学习 池化层学习 池化层&#xff08;Pooling Layer&#xff09;是卷积神经网络&#xff08;CNN&#xff09;中的一种重要组件&#xff0c;它的主要作用是逐步减小数据的空间尺寸&#xff08;即高度和宽度&#xff09;&#xff0c;以减少网络中参数的数量和计算量&a…