uniapp+Vue3实现音频文件解析及频谱柱状图支持小程序h5

uniapp实现音频文件解析及画频谱柱状图支持小程序h5

家人们,我废话少说,先看效果图(= ̄ω ̄=)喵了个咪!
在这里插入图片描述

先说一下技术栈

  1. uniapp+vue3+l-echat
  2. css引入了一个colorUI库,我觉得这个特别好用,基本不用写样式了
  3. 柱状图引用的是一个l-echart 封装好的,直接用
  4. 封装了一个音频解析js

我们直接讲代码
1.首先是dom树结构

<template>
	<view class="w100 bg-white">
		<view class="cu-bar bg-white">
			<view class="action" style="font-size: 30rpx;">
			 文件名:{{data.audioFileName}}
			</view>
			<view class="action">

			</view>
		</view>
		<LEchart class="echartDom" ref="chartRef" :customStyle="{width:'100%',height:'70vh'}" @finished="init"></LEchart>
		<view class="w100 padding-xs">
			<button class="cu-btn bg-blue" @click="playAudio">播放</button>
			<button class="cu-btn bg-blue margin-left-xs" @click="stopAudio">暂停</button>
		</view>
		<view class="w100 padding-xs">
			<button class="cu-btn margin-right-xs margin-bottom-xs" :class="{'bg-blue':data.active==index}"
				v-for="(item,index) in data.freqArray" :key="index" @click="ChangeRange(item,index)">{{item.label}}</button>

		</view>
	</view>
</template>

2.js相关逻辑

<script setup>
	import LEchart from '@/components/l-echart/l-echart.vue'
	// lime-echart是一个demo的组件,用于测试组件
	// import LEchart from '@/components/lime-echart/lime-echart.vue'
	import {
		onMounted,
		reactive,
		ref
	} from "vue"
	// nvue 不需要引入
	// #ifdef VUE3
	// #ifdef MP
	// 由于vue3 使用vite 不支持umd格式的包,小程序依然可以使用,但需要使用require
	const echarts = require('../../static/echarts.min.js');
	const {
		FFT,
		ComplexArray
	} = require('../../static/lib/fft.js');
	// #endif
	// #ifndef MP
	// 由于 vue3 使用vite 不支持umd格式的包,故引入npm的包
	import * as echarts from 'echarts';
	import {
		FFT,
		ComplexArray
	} from "../../static/lib/fft.js"
	// #endif
	// #endif
	// 柱状图初始化定义
	let chartRef = ref(null); // 获取dom
	let myChart=ref(null);
	const state = reactive({
		option: {},
	})
	state.option = {
		animation:false,
		legend: {
			show: true,
			data: []
		},
		tooltip: {
			trigger: 'axis',
			axisPointer: {
				type: 'cross'
			},
			formatter(){}
		},
		grid: {
			left: '5%',
			right: '10%',
			top: '1%',
			bottom: '1%',
			containLabel: true
		},
		xAxis: {
			name: 'Hz',
			type: 'category',
			data: [],
			axisLabel: {
				showMinLabel:true,
				showMaxLabel:true,
				color:'#999999'
				
			},
			axisTick: {
				show: false
			},
			axisLine: {
				show: true,
				lineStyle: {
					color: '#999999'
				}
			},
			z: 10
		},
		yAxis: {
			type: 'value',
			axisLine: {
				show: true,
				lineStyle: {
					color: '#e0e0e0'
				}
			},
			axisTick: {
				show: false
			},
			axisLabel: {
				show: false
			},
			splitLine: {
				show: false,
				lineStyle: {
					type: 'solid',
					color: '#e0e0e0'
				}
			}
		},
		dataZoom: [{
				min: 0,
				// 设置滚动条的隐藏与显示
				show: false,
				// 设置滚动条类型
				type: "slider",
				// 数据窗口范围的起始数值
				startValue: 0,
				// 数据窗口范围的结束数值(一页显示多少条数据)
				endValue: 1024,
			},
			{
				// 没有下面这块的话,只能拖动滚动条,
				// 鼠标滚轮在区域内不能控制外部滚动条
				type: "inside",
				// 滚轮是否触发缩放
				zoomOnMouseWheel: false,
				// 鼠标滚轮触发滚动
				moveOnMouseMove: true,
				moveOnMouseWheel: true,
			},
		],
		series: [{
			data: [],
			type: "bar",
			itemStyle: {
				color: '#188df0'
			},
		}],
		color: ['#83bff6']
	}

	// ****处理音频相关部分****
	let innerAudioContext=ref(null)
	let data = reactive({
		frequencyData: [], // 存储频谱数据
		// audioUrl: 'https://wlds.pqyjy.com/gateway/files/Devices/512Hz.wav', // Audio URL
		audioUrl: 'https://wlds.pqyjy.com/gateway//files/Devices/2024110003/20241113/2024110003_20241113123034.wav',
		audioFileName:'2024110003_20241113123034.wav',//音频文件名称
		audioPlaying: false, // 跟踪音频是否正在播放
		sampleRate: 0, // 采样率
		pcmData: [], // 音频的 PCM 数据
		freqArray: [{
				index: 0,
				min: null,
				max: null,
				label: '默认'
			},
			{
				index: 1,
				min: 200,
				max: 800,
				label: '[200~800]'
			},
			{
				index: 2,
				min: 250,
				max: 1000,
				label: '[250~1K]'
			},
			{
				index: 3,
				min: 250,
				max: 2000,
				label: '[250~2K]'
			},
			{
				index: 4,
				min: 100,
				max: 2000,
				label: '[100~2K]'
			},
		],
		active: 0,
	});

	// 从 URL 加载音频
	function loadAudio(fileUrl) {
		uni.request({
		    url: fileUrl,
		    responseType: 'arraybuffer', // 请求响应为二进制数据
		    success:(res) =>{
		        const uint8Array = new Uint8Array(res.data);
		        // 解析 WAV 文件头部,提取 PCM 数据和采样率等信息
		        const pcmInfo = parseWAVFile(uint8Array);
		            data.sampleRate= pcmInfo.sampleRate;
		            data.pcmData= pcmInfo.data;
					
		    },
		    fail(err) {
		        console.error('加载音频文件失败:', err);
		    }
		});
	}

	// 解析 WAV 文件头部,提取 PCM 数据和采样率等信息
	function parseWAVFile(wavData) {
		const buffer = wavData.buffer;
		const header = {
			sampleRate: (wavData[24] | (wavData[25] << 8) | (wavData[26] << 16) | (wavData[27] << 24)), // 解析采样率
			channels: wavData[22], // 声道数
			bitsPerSample: wavData[34] // 每个样本的位数
		};

		// 提取 PCM 数据
		const dataView = new DataView(buffer);
		const pcmStart = 44; // PCM 数据从第 44 字节开始
		const pcmData = [];

		for (let i = pcmStart; i < dataView.byteLength; i += 2) {
			pcmData.push(dataView.getInt16(i, true) / 32768); // 归一化 PCM 数据
		}

		return {
			data: pcmData,
			sampleRate: header.sampleRate,
			channels: header.channels,
			bitsPerSample: header.bitsPerSample
		};
	}

 // 根据当前播放时间更新 FFT 分析
   function updateFFT(currentTime) {
        const segmentSize = 1024; // 每次处理的 PCM 数据大小
        const startIndex = Math.floor(currentTime * data.sampleRate); // 根据当前时间计算 PCM 数据的起始索引
		// 确保 PCM 数据足够长,可以进行 FFT 计算
        if (startIndex + segmentSize > data.pcmData.length) {
            return;
        }

        // 获取当前时间段的 PCM 数据
        const segment = data.pcmData.slice(startIndex, startIndex + segmentSize);
        // 对当前段数据进行 FFT 计算
       performFFT(segment, data.sampleRate);
    }

    // 对 PCM 数据进行 FFT 分析
  function  performFFT(pcmSegment, sampleRate) {
        const fftSize = 1024;
        const complexArray = new ComplexArray(fftSize);

        // 填充复数数组用于 FFT 计算
        for (let i = 0; i < fftSize; i++) {
            complexArray.real[i] = pcmSegment[i] || 0; // 如果没有数据,则填充 0
            complexArray.imag[i] = 0;
        }
        // 执行 FFT 计算
        complexArray.FFT();

        // 处理频率数据
        const frequencyData = [];
        for (let i = 1; i < fftSize / 2; i++) { // 只取一半频率数据
            const real = complexArray.real[i];
            const imag = complexArray.imag[i];
            const magnitude = Math.sqrt(real ** 2 + imag ** 2); // 计算幅值
            const freq = (sampleRate / fftSize) * i; // 计算频率
            frequencyData.push({ frequency: freq, magnitude });
        }
        data.frequencyData = frequencyData;
        drawSpectrum(frequencyData); // 绘制频谱
    }
	// 绘制频谱图
	function drawSpectrum(frequencyData){
		
		const xData=[];
		const serData=[];
		// 把默认携带参数算出来
		if(data.active==0){
			if(frequencyData.length>0){
				data.freqArray[0].min=frequencyData[0].frequency;
				data.freqArray[0].max=frequencyData[frequencyData.length-1].frequency;
			}
		}
		// 拿到范围参数
     const range=data.freqArray[data.active];
		frequencyData.forEach((item)=>{
			if(item.frequency>=range.min&&item.frequency<=range.max){
			xData.push(item.frequency);
			serData.push(item.magnitude);
			}
		});
		state.option.xAxis.data=xData;
		state.option.series[0].data=serData;
		state.option.dataZoom[0].min=range.min;
		state.option.dataZoom[0].max=range.max;
		state.option.dataZoom[0].endValue=xData.length;
		myChart.setOption(state.option);
	}
	// 改变范围
	function ChangeRange(item,index){
		if(index==data.active){
			return;
		}else{
			data.active=index;
			drawSpectrum(data.frequencyData);
		}
		
	}
	
	
	
	
	
	   // 点击播放按钮时播放音频
	 function   playAudio() {
	        if (!data.audioPlaying) {
	            innerAudioContext.play(); // 播放音频
	        }
	    }
	
	    // 点击停止按钮时停止音频播放
	 function   stopAudio() {
	        if (data.audioPlaying) {
	            innerAudioContext.stop(); // 停止音频播放
	        }
	    }
	
	
	// 组件能被调用必须是组件的节点已经被渲染到页面上
	onMounted(() => {
		// 组件能被调用必须是组件的节点已经被渲染到页面上
		setTimeout(async () => {
			if (!chartRef.value) return
			 myChart = await chartRef.value.init(echarts);
			myChart.setOption(state.option)
		}, 300);

		// 音频相关处理部分

		loadAudio(data.audioUrl);

		// 初始化 innerAudioContext,用于播放音频
		 innerAudioContext = uni.createInnerAudioContext();
		innerAudioContext.src = data.audioUrl;
		innerAudioContext.autoplay = false; // 设置为 false,手动控制播放
		// 处理音频播放事件
		innerAudioContext.onPlay(() => {
			console.log("音频播放开始");
			data.audioPlaying = true;
		});

		innerAudioContext.onPause(() => {
			console.log("音频播放暂停");
			data.audioPlaying = false;
		});

		innerAudioContext.onStop(() => {
			console.log("音频播放停止");
			data.audioPlaying = false;
		});

		innerAudioContext.onEnded(() => {
			console.log("音频播放结束");
			data.audioPlaying = false;
	});

	// 当音频播放时实时更新频谱
	innerAudioContext.onTimeUpdate(() => {
	const currentTime = innerAudioContext.currentTime;
	updateFFT(currentTime); // 根据当前时间更新 FFT
	});



	})

	// 渲染完成
	const init = () => {
		console.log("渲染完成");
	}
</script>


这样就大功告成啦!!! 如果帮助到你记得点赞加收藏哦!!!!!

如果需要源码可加wx:x2251774980

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值