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

本文主要是介绍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 音频可视化

这篇关于RK3568 Android 11 蓝牙BluetoothA2dpSink 获取用于生成频谱的PCM的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1115448

相关文章

SpringBoot服务获取Pod当前IP的两种方案

《SpringBoot服务获取Pod当前IP的两种方案》在Kubernetes集群中,SpringBoot服务获取Pod当前IP的方案主要有两种,通过环境变量注入或通过Java代码动态获取网络接口IP... 目录方案一:通过 Kubernetes Downward API 注入环境变量原理步骤方案二:通过

使用Python实现获取屏幕像素颜色值

《使用Python实现获取屏幕像素颜色值》这篇文章主要为大家详细介绍了如何使用Python实现获取屏幕像素颜色值,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 一、一个小工具,按住F10键,颜色值会跟着显示。完整代码import tkinter as tkimport pyau

Python实现自动化Word文档样式复制与内容生成

《Python实现自动化Word文档样式复制与内容生成》在办公自动化领域,高效处理Word文档的样式和内容复制是一个常见需求,本文将展示如何利用Python的python-docx库实现... 目录一、为什么需要自动化 Word 文档处理二、核心功能实现:样式与表格的深度复制1. 表格复制(含样式与内容)2

python获取cmd环境变量值的实现代码

《python获取cmd环境变量值的实现代码》:本文主要介绍在Python中获取命令行(cmd)环境变量的值,可以使用标准库中的os模块,需要的朋友可以参考下... 前言全局说明在执行py过程中,总要使用到系统环境变量一、说明1.1 环境:Windows 11 家庭版 24H2 26100.4061

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

python如何生成指定文件大小

《python如何生成指定文件大小》:本文主要介绍python如何生成指定文件大小的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录python生成指定文件大小方法一(速度最快)方法二(中等速度)方法三(生成可读文本文件–较慢)方法四(使用内存映射高效生成

Maven项目中集成数据库文档生成工具的操作步骤

《Maven项目中集成数据库文档生成工具的操作步骤》在Maven项目中,可以通过集成数据库文档生成工具来自动生成数据库文档,本文为大家整理了使用screw-maven-plugin(推荐)的完... 目录1. 添加插件配置到 pom.XML2. 配置数据库信息3. 执行生成命令4. 高级配置选项5. 注意事

使用Python获取JS加载的数据的多种实现方法

《使用Python获取JS加载的数据的多种实现方法》在当今的互联网时代,网页数据的动态加载已经成为一种常见的技术手段,许多现代网站通过JavaScript(JS)动态加载内容,这使得传统的静态网页爬取... 目录引言一、动态 网页与js加载数据的原理二、python爬取JS加载数据的方法(一)分析网络请求1

电脑蓝牙连不上怎么办? 5 招教你轻松修复Mac蓝牙连接问题的技巧

《电脑蓝牙连不上怎么办?5招教你轻松修复Mac蓝牙连接问题的技巧》蓝牙连接问题是一些Mac用户经常遇到的常见问题之一,在本文章中,我们将提供一些有用的提示和技巧,帮助您解决可能出现的蓝牙连接问... 蓝牙作为一种流行的无线技术,已经成为我们连接各种设备的重要工具。在 MAC 上,你可以根据自己的需求,轻松地

通过cmd获取网卡速率的代码

《通过cmd获取网卡速率的代码》今天从群里看到通过bat获取网卡速率两段代码,感觉还不错,学习bat的朋友可以参考一下... 1、本机有线网卡支持的最高速度:%v%@echo off & setlocal enabledelayedexpansionecho 代码开始echo 65001编码获取: >