android 音效可视化--Visualizer

        Visualizer 是使应用程序能够检索当前播放音频的一部分以进行可视化。它不是录音接口,仅返回部分低质量的音频内容。但是,为了保护某些音频数据的隐私,使用 Visualizer 需要 android.permission.RECORD_AUDIO权限。传递给构造函数的音频会话 ID 指示应可视化哪些音频内容:

  • 如果会话为 0,则音频输出混合可视化
  • 如果会话不为 0,则显示来自特定会话android.media.MediaPlayer或使用此音频会话的音频android.media.AudioTrack

可以捕获两种类型的音频内容表现形式:

  • 波形数据:使用该getWaveForm(byte[])方法连续的8位(无符号)单声道样本
  • 频率数据:采用8位幅度FFTgetFft(byte[])方法

捕获的长度可以通过分别调用getCaptureSize()setCaptureSize(int)方法来检索或指定。捕获大小必须是返回范围内的 2 的幂getCaptureSizeRange()

1. 权限请求

需要在Manifest里面添加

 <uses-permission android:name="android.permission.RECORD_AUDIO" />

RECORD_AUDIOAndroid 6.0后需要动态请求权限

val audioPermission =ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)if (audioPermission != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this,permissions,PERMISSION_REQUEST_CODE /* your request code */)}
2. 初始化Visualizer

首先初始化MediaPlayer,初始MediaPlayer是为了拿到audioSessionId

  MusicPlayHelper.init(application, object : IMusicPlayListener {override fun loadMusicFinish(boolean: Boolean, position: Int) {MusicPlayHelper.play()}})

初始化Visualizer,musicId就是上面的audioSessionId,如果传0是全局改变,但是有可能会报错。

   if (mVisualizer == null) {mVisualizer = Visualizer(musicId)}mVisualizer?.enabled = falsemVisualizer?.captureSize = Visualizer.getCaptureSizeRange()[1]mVisualizer?.setDataCaptureListener(captureListener,Visualizer.getMaxCaptureRate() / 2,true,true)// Enabled Visualizer and disable when we're done with the streammVisualizer?.enabled = true

setDataCaptureListener 为可视化对象设置采样监听数据的回调,setDataCaptureListener的参数作用如下:

listener:回调对象
rate:采样的频率,其范围是0~Visualizer.getMaxCaptureRate(),此处设置为最大值一半。
waveform:是否获取波形信息
fft:是否获取快速傅里叶变换后的数据

OnDataCaptureListener中的两个回调方法分别为:

onWaveFormDataCapture:波形数据回调
onFftDataCapture:傅里叶数据回调,即频率数据回调

3.设置Visualizer是否接收数据

enable为true正常接收,为false关闭

fun setVisualizerEnable(flag: Boolean) {mVisualizer?.enabled = flag
}
4. 释放Visualizer

使用完需要调用release方法释放

    fun release() {mVisualizer?.enabled = falsemVisualizer?.release()mVisualizer = null}

完整VisualizerView代码

package com.example.knowledgemanagement.visualizerimport android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.media.audiofx.Visualizer
import android.media.audiofx.Visualizer.OnDataCaptureListener
import android.util.AttributeSet
import android.view.View
import com.xing.commonlibrary.log.LogUtilsclass VisualizerView @JvmOverloads constructor(context: Context?,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) :View(context, attrs, defStyleAttr) {private val TAG = "VisualizerView"private var mBytes: ByteArray? = nullprivate var mFFTBytes: ByteArray? = nullprivate val mRect = Rect()private var mVisualizer: Visualizer? = nullprivate var mRenderers: MutableSet<Renderer> = HashSet()var left1 = 0var top1 = 0var right1 = 0var bottom1 = 0private var mCanvas: Canvas? = nullprivate var mAudioSamplingTime: Long = 0private var mFftSamplingTime: Long = 0private val mSamplingTime = 100 //数据采样时间间隔private var isLink = falseinit {setLayerType(LAYER_TYPE_SOFTWARE, null) //禁止硬件加速init()}private fun init() {mBytes = nullmFFTBytes = null}override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {super.onLayout(changed, left, top, right, bottom)left1 = lefttop1 = topright1 = rightbottom1 = bottom}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)mCanvas = canvasmRect[0, 0, width] = heightmBytes?.let {// Render all audio renderersfor (r in mRenderers) {r.audioRender(canvas, it, mRect)}}mFFTBytes?.let {// Render all FFT renderersfor (r in mRenderers) {r.fftRender(canvas, it, mRect)}}}fun link(musicId: Int) {try {//使用前先释放if (mVisualizer != null) {release()}if (mVisualizer == null && !isLink) {mVisualizer = Visualizer(musicId)isLink = true}LogUtils.e(TAG, "nsc =" + mVisualizer?.enabled)mVisualizer?.enabled = falsemVisualizer?.captureSize = Visualizer.getCaptureSizeRange()[1]// Pass through Visualizer data to VisualizerViewval captureListener: OnDataCaptureListener = object : OnDataCaptureListener {override fun onWaveFormDataCapture(visualizer: Visualizer, bytes: ByteArray,samplingRate: Int) {val currentTimeMillis = System.currentTimeMillis()LogUtils.i(TAG, "onWaveFormDataCapture")if (currentTimeMillis - mAudioSamplingTime >= mSamplingTime) {mBytes = bytesinvalidate()mAudioSamplingTime = currentTimeMillis}}override fun onFftDataCapture(visualizer: Visualizer, bytes: ByteArray,samplingRate: Int) {LogUtils.i(TAG, "onFftDataCapture")val currentTimeMillis = System.currentTimeMillis()if (currentTimeMillis - mFftSamplingTime >= mSamplingTime) {mFFTBytes = bytesinvalidate()mFftSamplingTime = currentTimeMillis}}}mVisualizer?.setDataCaptureListener(captureListener,Visualizer.getMaxCaptureRate() / 2,true,true)// Enabled Visualizer and disable when we're done with the streammVisualizer?.enabled = true} catch (e: RuntimeException) {}}fun setVisualizerEnable(flag: Boolean) {mVisualizer?.enabled = flag}fun release() {mVisualizer?.enabled = falsemVisualizer?.release()mVisualizer = nullisLink = false}fun addRenderer(renderer: Renderer?) {if (renderer != null) {mRenderers.add(renderer)}}fun clearRenderers() {mRenderers.clear()}
}
5. 实现简单的柱状显示

实现很简单,就是将拿到的数据通过canvas.drawLines绘制出来,

class ColumnarRenderer( private val mPaint: Paint) : Renderer() {private val mSpectrumNum = 96override fun onAudioRender(canvas: Canvas, data: ByteArray, rect: Rect) {}override fun onFftRender(canvas: Canvas, data: ByteArray, rect: Rect) {val baseX = rect.width() / mSpectrumNumval height = rect.height()for (i in 0 until mSpectrumNum) {val magnitude = (baseX * i + baseX / 2).toFloat()mFFTPoints?.let {it[i * 4] = magnitudeit[i * 4 + 1] = (height / 2).toFloat()it[i * 4 + 2] = magnitudeit[i * 4 + 3] = (height / 2 - data[i] * 4).toFloat()}}mFFTPoints?.let { canvas.drawLines(it, mPaint) }}
}

然后调用visualizerView添加到Renderer即可

 visualizerView.addRenderer(columnarRenderer);
6.实现能量块跳动

代码里面有详细备注

package com.example.knowledgemanagement.visualizerimport android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import java.util.Random
import kotlin.math.abs
import kotlin.math.hypotclass EnergyBlockRenderer(private val mPaint: Paint) : Renderer() {companion object {private const val TAG = "EnergyBlockRenderer"private const val MAX_LEVEL = 30 //音量柱·音频块 - 最大个数private const val CYLINDER_NUM = 26 //音量柱 - 最大个数private const val DN_W = 470 //view宽度与单个音频块占比 - 正常480 需微调private const val DN_H = 300 //view高度与单个音频块占比private const val DN_SL = 10 //单个音频块宽度private const val DN_SW = 2 //单个音频块高度}private var mData = ByteArray(CYLINDER_NUM) //音量柱 数组private var hGap = 0private var vGap = 0private var levelStep = 0private var strokeWidth = 0fprivate var strokeLength = 0fvar mDataEn = trueinit {levelStep = 230 / MAX_LEVEL}fun onLayout(left: Int, top: Int, right: Int, bottom: Int) {val w: Float = (right - left).toFloat()val h: Float = (bottom - top).toFloat()val xr: Float = w / DN_W.toFloat()val yr: Float = h / DN_H.toFloat()strokeWidth = DN_SW * yrstrokeLength = DN_SL * xrhGap = ((w - strokeLength * CYLINDER_NUM) / (CYLINDER_NUM + 1)).toInt()vGap = (h / (MAX_LEVEL + 2)).toInt() //频谱块高度mPaint.strokeWidth = strokeWidth //设置频谱块宽度}//绘制频谱块和倒影private fun drawCylinder(canvas: Canvas, x: Float, value: Byte, rect: Rect) {var value = valueif (value.toInt() == 0) {value = 1} //最少有一个频谱块for (i in 0 until value) { //每个能量柱绘制value个能量块val y = (rect.height() / 2 - i * vGap / 2 - vGap).toFloat() //计算y轴坐标val y1 = (rect.height() / 2 + i * vGap / 2 + vGap).toFloat()//绘制频谱块mPaint.color = color //画笔颜色canvas.drawLine(x, y, x + strokeLength, y, mPaint) //绘制频谱块//绘制音量柱倒影if (i <= 6 && value > 0) {mPaint.color = Color.WHITE //画笔颜色mPaint.alpha = 100 - 100 / 6 * i //倒影颜色canvas.drawLine(x, y1, x + strokeLength, y1, mPaint) //绘制频谱块}}}private val color: Intprivate get() {val ranColor = intArrayOf(Color.RED, Color.YELLOW, Color.MAGENTA, Color.BLUE, Color.GREEN, Color.GRAY,Color.CYAN, Color.LTGRAY, Color.TRANSPARENT)val random = Random()val value = random.nextInt(ranColor.size - 1)return ranColor[value]}override fun onAudioRender(canvas: Canvas, data: ByteArray, rect: Rect) {}override fun onFftRender(canvas: Canvas, data: ByteArray, rect: Rect) {val model = ByteArray(data.size / 2 + 1)if (mDataEn) {model[0] = abs(data[1].toInt()).toByte()var j = 1var i = 2while (i < data.size) {model[j] = hypot(data[i].toDouble(), data[i + 1].toDouble()).toInt().toByte()i += 2j++}} else {for (i in 0 until CYLINDER_NUM) {model[i] = 0}}for (i in 0 until CYLINDER_NUM) {val a = (abs(model[CYLINDER_NUM - i].toInt()) / levelStep).toByte()val b = mData[i]if (a > b) {mData[i] = a} else {if (b > 0) {mData[i]--}}}var j = -4for (i in 0 until CYLINDER_NUM / 2 - 4) {drawCylinder(canvas, strokeWidth / 2 + hGap + i * (hGap + strokeLength), mData[i], rect)}for (i in CYLINDER_NUM downTo CYLINDER_NUM / 2 - 4) {j++drawCylinder(canvas, strokeWidth / 2 + hGap + (CYLINDER_NUM / 2 + j - 1) * (hGap + strokeLength), mData[i - 1], rect)}}}

Render代码

package com.example.knowledgemanagement.visualizerimport android.graphics.Canvas
import android.graphics.Rectabstract class Renderer {// Have these as members, so we don't have to re-create them each timevar mPoints: FloatArray? = nullvar mFFTPoints: FloatArray? = nullvar isPlaying = true// As the display of raw/FFT audio will usually look different, subclasses// will typically only implement one of the below methods/*** Implement this method to audioRender the audio data onto the canvas** @param canvas - Canvas to draw on* @param data   - Data to audioRender* @param rect   - Rect to audioRender into*/abstract fun onAudioRender(canvas: Canvas, data: ByteArray, rect: Rect)/*** Implement this method to audioRender the FFT audio data onto the canvas** @param canvas - Canvas to draw on* @param data   - Data to audioRender* @param rect   - Rect to audioRender into*/abstract fun onFftRender(canvas: Canvas, data: ByteArray, rect: Rect)// These methods should actually be called for rendering/*** Render the audio data onto the canvas** @param canvas - Canvas to draw on* @param data   - Data to audioRender* @param rect   - Rect to audioRender into*/fun audioRender(canvas: Canvas, data: ByteArray, rect: Rect) {if (mPoints == null || mPoints!!.size < data.size * 4) {mPoints = FloatArray(data.size * 4)}onAudioRender(canvas, data, rect)}/*** Render the FFT data onto the canvas** @param canvas - Canvas to draw on* @param data   - Data to audioRender* @param rect   - Rect to audioRender into*/fun fftRender(canvas: Canvas, data: ByteArray, rect: Rect) {if (mFFTPoints == null || mFFTPoints!!.size < data.size * 4) {mFFTPoints = FloatArray(data.size * 4)}onFftRender(canvas, data, rect)}
}
7. 错误原因跟解决办法

下面错误是因为没有获取音乐的SessionId传入,传了0导致的问题。

The Visualizer initCheck failed -3 error typically occurs due to missing 
permissions, invalid audio session IDs, hardware limitations, or timing issues. 
By addressing these potential causes, you should be able to resolve the issue and 
successfully initialize the Visualizer in your Android application.

想看更详细的介绍可以看谷歌文档:Visualizer  |  Android Developers

代码下载:https://download.csdn.net/download/u011324501/90038203

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

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

相关文章

计算机网络八股整理(一)

计算机网络八股文整理 一&#xff1a;网络模型 1&#xff1a;网络osi模型和tcp/ip模型分别介绍一下 osi模型是国际标准的网络模型&#xff0c;它由七层组成&#xff0c;从上到下分别是&#xff1a;应用层&#xff0c;表示层&#xff0c;会话层&#xff0c;传输层&#xff0c;…

利用Python爬虫获得1688按关键字搜索商品:技术解析

在电商领域&#xff0c;1688作为中国领先的B2B电商平台&#xff0c;其商品搜索功能对于商家来说具有极高的价值。通过获取搜索结果&#xff0c;商家可以更好地了解市场趋势&#xff0c;优化产品标题&#xff0c;提高搜索排名。本文将介绍如何使用Python编写爬虫&#xff0c;以获…

Spring Boot集成MyBatis-Plus:自定义拦截器实现动态表名切换

Spring Boot集成MyBatis-Plus&#xff1a;自定义拦截器实现动态表名切换 一、引言 介绍动态表名的场景需求&#xff0c;比如多租户系统、分表分库&#xff0c;或者不同业务模块共用一套代码但操作不同表。说明 MyBatis-Plus 默认绑定固定表名的问题。 二、项目配置 1. 集成 M…

(原创)Android Studio新老界面UI切换及老版本下载地址

前言 这两天下载了一个新版的Android Studio&#xff0c;发现整个界面都发生了很大改动&#xff1a; 新的界面的一些设置可参考一些博客&#xff1a; Android Studio新版UI常用设置 但是对于一些急着开发的小伙伴来说&#xff0c;没有时间去适应&#xff0c;那么怎么办呢&am…

数据新时代:如何选择现代数据治理平台(上)

谈现代数据治理系统的十大架构特征 最近一位老友找到我&#xff0c;咨询他的数据治理平台到底该不该换&#xff0c;背景是这样的&#xff1a;若干年前采购了一个市场主流的数据治理平台&#xff0c;功能大概就是数据治理三件套——标准、元数据和质量等经典数据治理的功能。现…

抖音SEO矩阵系统:开发技术分享

市场环境剖析 短视频SEO矩阵系统是一种策略&#xff0c;旨在通过不同平台上的多个账号建立联系&#xff0c;整合同一品牌下的各平台粉丝流量。该系统通过遵循每个平台的规则和内容要求&#xff0c;输出企业和品牌形象&#xff0c;以矩阵形式增强粉丝基础并提升商业价值。抖音作…

从零开始打造个人博客:我的网页设计之旅

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 ✨特色专栏&#xff1a…

STM32F103C8T6实时时钟RTC

目录 前言 一、RTC基本硬件结构 二、Unix时间戳 2.1 unix时间戳定义 2.2 时间戳与日历日期时间的转换 2.3 指针函数使用注意事项 ​三、RTC和BKP硬件结构 四、驱动代码解析 前言 STM32F103C8T6外部低速时钟LSE&#xff08;一般为32.768KHz&#xff09;用的引脚是PC14和PC…

Jmeter中的定时器

4&#xff09;定时器 1--固定定时器 功能特点 固定延迟&#xff1a;在每个请求之间添加固定的延迟时间。精确控制&#xff1a;可以精确控制请求的发送频率。简单易用&#xff1a;配置简单&#xff0c;易于理解和使用。 配置步骤 添加固定定时器 右键点击需要添加定时器的请求…

Fakelocation Server服务器/专业版 ubuntu

前言:需要Ubuntu系统 Fakelocation开源文件系统需求 Ubuntu | Fakelocation | 任务一 任务一 更新Ubuntu&#xff08;安装下载不再赘述&#xff09; sudo -i # 提权 sudo apt update # 更新软件包列表 sudo apt upgrade # 升级已安装的软…

5.5 W5500 TCP服务端与客户端

文章目录 1、TCP介绍2、W5500简介2.1 关键函数socketlistensendgetSn_RX_RSRrecv自动心跳包检测getSn_SR 1、TCP介绍 TCP 服务端&#xff1a; 创建套接字[socket]&#xff1a;服务器首先创建一个套接字&#xff0c;这是网络通信的端点。绑定套接字[bind]&#xff1a;服务器将…

超高流量多级缓存架构设计!

文章内容已经收录在《面试进阶之路》&#xff0c;从原理出发&#xff0c;直击面试难点&#xff0c;实现更高维度的降维打击&#xff01; 文章目录 电商-多级缓存架构设计多级缓存架构介绍多级缓存请求流程负载均衡算法的选择轮询负载均衡一致性哈希负载均衡算法选择 应用层 Ngi…

信创改造 - TongRDS 替换 Redis

记得开放 6379 端口哦 1&#xff09;首先在服务器上安装好 TongRDS 2&#xff09;替换 redis 的 host&#xff0c;post&#xff0c;passwd 3&#xff09;TongRDS 兼容 jedis # 例如&#xff1a;更改原先 redis 中对应的 host&#xff0c;post&#xff0c;passwd 改成 TongRDS…

vue3 uniapp 扫普通链接或二维码打开小程序并获取携带参数

vue3 uniapp 扫普通链接或二维码打开小程序并获取携带参数 微信公众平台添加配置 微信公众平台 > 开发管理 > 开发设置 > 扫普通链接二维码打开小程序 配置链接规则需要下载校验文档给后端存入服务器中&#xff0c;保存配置的时候会校验一次&#xff0c;确定当前的配…

Git(一)基本使用

目录 一、使用git -v 查看安装git版本 二、使用mkdir 创建一个文件&#xff0c;并使用 git init 在该目录下创建一个本地仓库&#xff0c; 三、通过git clone命令接入线上仓库 四、使用git status查看仓库状态信息 五、利用echo写入一个文件 并使用cat进行查看 【Linux】e…

QML学习 —— 29、3种不同使用动画的方式(附源码)

效果 说明 第一种:属性动画 - 当启动软件时候自动执行动画。      第二种:行为动画 - 当属性发生变化则自动执行动画。      第三种:目标动画 - 将动画变为对象,指定对象的目标进行执行动画。 代码 import QtQuick 2.12 import QtQuick.Window 2.12 import QtQu…

下载并安装Visual Studio 2017过程

一、下载 1、下载链接 下载链接&#xff1a;官方网址 先登录 往下滑找到较早的下载 2、进行搜索下载 或者直接点击&#x1f517;网站跳转 3、确认系统信息进行下载 二、安装 下载完成后右键使用管理员身份运行 1、点击同意后安装 2、若报错—设置失败 打开控制面板-&g…

React(五)——useContecxt/Reducer/useCallback/useRef/React.memo/useMemo

文章目录 项目地址十六、useContecxt十七、useReducer十八、React.memo以及产生的问题18.1组件嵌套的渲染规律18.2 React.memo18.3 引出问题 十九、useCallback和useMemo19.1 useCallback对函数进行缓存19.2 useMemo19.2.1 基本的使用19.2.2 缓存属性数据 19.2.3 对于更新的理解…

vue实现列表滑动下拉加载数据

一、实现效果 二、实现思路 使用滚动事件监听器来检测用户是否滚动到底部&#xff0c;然后加载更多数据 监听滚动事件。检测用户是否滚动到底部。加载更多数据。 三、案例代码 <div class"drawer-content"><div ref"loadMoreTrigger" class&q…

Redis常见面试题总结(上)

Redis 基础 什么是 Redis&#xff1f; Redis &#xff08;REmote DIctionary Server&#xff09;是一个基于 C 语言开发的开源 NoSQL 数据库&#xff08;BSD 许可&#xff09;。与传统数据库不同的是&#xff0c;Redis 的数据是保存在内存中的&#xff08;内存数据库&#xf…