uni-app 音频处理
核心知识点
1. 音频基础概念
- 音频格式:MP3、WAV、AAC、OGG、FLAC 等
- 音频参数:采样率、位深度、声道数、码率
- 音频文件大小:与参数设置的关系
- 音频质量:不同格式和参数的音质差异
2. 音频录制
- 录制 API:uni.getRecorderManager()
- 录制配置:格式、采样率、码率等
- 录制控制:开始、暂停、恢复、停止
- 录制状态监听:开始、暂停、停止、错误等
- 录音权限:需要获取麦克风权限
3. 音频播放
- 播放 API:uni.createInnerAudioContext()
- 播放控制:播放、暂停、停止、快进、快退
- 音量控制:系统音量和播放器音量
- 播放状态监听:播放中、暂停、停止、结束、错误等
- 音频焦点:处理音频中断和恢复
4. 音频编辑
- 音频裁剪:截取音频片段
- 音频合并:将多个音频文件合并为一个
- 音频转换:格式转换、参数调整
- 音频特效:添加音效、混响、均衡器等
- 音频分析:波形分析、频谱分析
5. 音频优化
- 音频加载优化:预加载、缓存策略
- 音频播放优化:低延迟播放、无缝播放
- 内存管理:及时释放音频资源
- 电池优化:减少后台音频消耗
- 网络优化:音频流传输优化
6. 跨平台音频处理
- 平台差异:不同平台对音频格式和 API 的支持
- 条件编译:为不同平台提供不同的音频处理方案
- 原生能力:使用平台原生的音频处理 API
- 第三方库:使用跨平台音频处理库
实用案例
案例 1:语音备忘录应用
需求分析
- 支持录制语音备忘录
- 支持播放、暂停、删除录音
- 显示录音时长和文件大小
- 支持录音列表管理
实现方案
音频录制
<template> <view class="record-section"> <button @click="toggleRecord" :type="isRecording ? 'warn' : 'primary'" class="record-btn" > {{ isRecording ? '停止录音' : '开始录音' }} </button> <text class="record-duration" v-if="isRecording"> 录音时长: {{ formatTime(duration) }} </text> <view class="录音列表"> <view v-for="(item, index) in recordings" :key="index" class="recording-item"> <view class="recording-info"> <text class="recording-name">{{ item.name }}</text> <text class="recording-duration">{{ formatTime(item.duration) }}</text> </view> <view class="recording-actions"> <button @click="playRecording(item)" size="mini" type="primary">播放</button> <button @click="deleteRecording(index)" size="mini" type="warn">删除</button> </view> </view> </view> </view> </template> <script> export default { data() { return { recorderManager: null, innerAudioContext: null, isRecording: false, isPlaying: false, duration: 0, recordings: [], timer: null } }, onLoad() { this.initRecorder() this.initPlayer() this.loadRecordings() }, onUnload() { // 释放资源 if (this.recorderManager) { this.recorderManager.stop() } if (this.innerAudioContext) { this.innerAudioContext.stop() this.innerAudioContext.destroy() } if (this.timer) { clearInterval(this.timer) } }, methods: { initRecorder() { this.recorderManager = uni.getRecorderManager() this.recorderManager.onStart(() => { console.log('开始录音') this.isRecording = true this.duration = 0 this.timer = setInterval(() => { this.duration++ }, 1000) }) this.recorderManager.onStop((res) => { console.log('停止录音', res) this.isRecording = false if (this.timer) { clearInterval(this.timer) this.timer = null } const recording = { name: `录音 ${new Date().toLocaleString()}`, path: res.tempFilePath, duration: this.duration, size: res.fileSize } this.recordings.push(recording) this.saveRecordings() }) this.recorderManager.onError((err) => { console.error('录音错误', err) this.isRecording = false if (this.timer) { clearInterval(this.timer) this.timer = null } }) }, initPlayer() { this.innerAudioContext = uni.createInnerAudioContext() this.innerAudioContext.onPlay(() => { console.log('开始播放') this.isPlaying = true }) this.innerAudioContext.onPause(() => { console.log('暂停播放') this.isPlaying = false }) this.innerAudioContext.onStop(() => { console.log('停止播放') this.isPlaying = false }) this.innerAudioContext.onEnded(() => { console.log('播放结束') this.isPlaying = false }) this.innerAudioContext.onError((err) => { console.error('播放错误', err) this.isPlaying = false }) }, toggleRecord() { if (this.isRecording) { this.recorderManager.stop() } else { // 检查权限 uni.getSetting({ success: (res) => { if (!res.authSetting['scope.record']) { uni.authorize({ scope: 'scope.record', success: () => { this.startRecording() }, fail: () => { uni.showToast({ title: '需要麦克风权限才能录音', icon: 'none' }) } }) } else { this.startRecording() } } }) } }, startRecording() { this.recorderManager.start({ format: 'mp3', sampleRate: 44100, numberOfChannels: 2, encodeBitRate: 128000 }) }, playRecording(recording) { if (this.isPlaying) { this.innerAudioContext.stop() } this.innerAudioContext.src = recording.path this.innerAudioContext.play() }, deleteRecording(index) { uni.showModal({ title: '确认删除', content: '确定要删除这个录音吗?', success: (res) => { if (res.confirm) { this.recordings.splice(index, 1) this.saveRecordings() } } }) }, saveRecordings() { uni.setStorageSync('recordings', this.recordings) }, loadRecordings() { const recordings = uni.getStorageSync('recordings') if (recordings) { this.recordings = recordings } }, formatTime(seconds) { const minutes = Math.floor(seconds / 60) const remainingSeconds = seconds % 60 return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}` } } } </script>录音列表管理
- 使用本地存储保存录音列表
- 支持录音重命名和分类
- 实现录音搜索和排序功能
案例 2:音频播放器应用
需求分析
- 支持播放本地和网络音频文件
- 支持音频控制:播放、暂停、上一曲、下一曲
- 显示音频播放进度和时长
- 支持音频列表管理和随机播放
- 支持音频均衡器和音效设置
实现方案
音频播放
<template> <view class="player-section"> <view class="player-info"> <text class="audio-title">{{ currentAudio.title }}</text> <text class="audio-artist">{{ currentAudio.artist }}</text> </view> <view class="progress-section"> <text class="time">{{ formatTime(currentTime) }}</text> <view class="progress-bar" @click="seek"> <view class="progress-track"></view> <view class="progress-fill" :style="{ width: `${progress}%` }"></view> <view class="progress-thumb" :style="{ left: `${progress}%` }"></view> </view> <text class="time">{{ formatTime(duration) }}</text> </view> <view class="control-section"> <button @click="prev" class="control-btn">上一曲</button> <button @click="togglePlay" class="control-btn primary"> {{ isPlaying ? '暂停' : '播放' }} </button> <button @click="next" class="control-btn">下一曲</button> </view> <view class="eq-section"> <text class="eq-title">均衡器</text> <view class="eq-presets"> <button v-for="(preset, index) in eqPresets" :key="index" @click="setEQPreset(preset)" :class="{ active: currentPreset === preset }" class="eq-preset-btn" > {{ preset }} </button> </view> </view> </view> </template> <script> export default { data() { return { innerAudioContext: null, audioList: [ { id: 1, title: '歌曲 1', artist: '艺术家 1', url: 'https://example.com/song1.mp3' }, { id: 2, title: '歌曲 2', artist: '艺术家 2', url: 'https://example.com/song2.mp3' }, { id: 3, title: '歌曲 3', artist: '艺术家 3', url: 'https://example.com/song3.mp3' } ], currentIndex: 0, isPlaying: false, currentTime: 0, duration: 0, progress: 0, eqPresets: ['正常', '摇滚', '流行', '爵士', '古典'], currentPreset: '正常' } }, computed: { currentAudio() { return this.audioList[this.currentIndex] } }, onLoad() { this.initPlayer() this.loadAudio(this.currentAudio.url) }, onUnload() { if (this.innerAudioContext) { this.innerAudioContext.stop() this.innerAudioContext.destroy() } }, methods: { initPlayer() { this.innerAudioContext = uni.createInnerAudioContext() this.innerAudioContext.onPlay(() => { console.log('开始播放') this.isPlaying = true }) this.innerAudioContext.onPause(() => { console.log('暂停播放') this.isPlaying = false }) this.innerAudioContext.onStop(() => { console.log('停止播放') this.isPlaying = false }) this.innerAudioContext.onEnded(() => { console.log('播放结束') this.isPlaying = false this.next() }) this.innerAudioContext.onTimeUpdate(() => { this.currentTime = this.innerAudioContext.currentTime this.duration = this.innerAudioContext.duration || 0 this.progress = (this.currentTime / this.duration) * 100 }) this.innerAudioContext.onError((err) => { console.error('播放错误', err) this.isPlaying = false }) }, loadAudio(url) { this.innerAudioContext.src = url this.innerAudioContext.play() }, togglePlay() { if (this.isPlaying) { this.innerAudioContext.pause() } else { this.innerAudioContext.play() } }, prev() { this.currentIndex = (this.currentIndex - 1 + this.audioList.length) % this.audioList.length this.loadAudio(this.currentAudio.url) }, next() { this.currentIndex = (this.currentIndex + 1) % this.audioList.length this.loadAudio(this.currentAudio.url) }, seek(e) { const { clientX, target } = e const rect = target.getBoundingClientRect() const percentage = (clientX - rect.left) / rect.width const seekTime = percentage * this.duration this.innerAudioContext.seek(seekTime) }, setEQPreset(preset) { this.currentPreset = preset // 这里可以实现均衡器设置 console.log('设置均衡器预设:', preset) }, formatTime(seconds) { if (!seconds) return '00:00' const minutes = Math.floor(seconds / 60) const remainingSeconds = Math.floor(seconds % 60) return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}` } } } </script>音频列表管理
- 支持本地音频扫描和网络音频添加
- 实现音频分类和播放列表管理
- 支持音频搜索和排序功能
音频优化
- 实现音频预加载,减少播放延迟
- 使用音频缓存,减少网络请求
- 优化后台播放,提升用户体验
案例 3:语音识别应用
需求分析
- 支持实时语音识别
- 支持语音转文本
- 支持文本转语音
- 支持语音命令控制
实现方案
- 语音识别
<template> <view class="speech-section"> <button @click="toggleSpeechRecognition" :type="isRecognizing ? 'warn' : 'primary'" class="speech-btn" > {{ isRecognizing ? '停止识别' : '开始识别' }} </button> <view class="result-section"> <text class="result-title">识别结果</text> <text class="result-text">{{ recognitionResult }}</text> </view> <button @click="textToSpeech" type="primary" class="tts-btn"> 文本转语音 </button> </view> </template> <script> export default { data() { return { isRecognizing: false, recognitionResult: '', ttsContext: null } }, onLoad() { this.initTTS() }, methods: { toggleSpeechRecognition() { if (this.isRecognizing) { this.stopRecognition() } else { this.startRecognition() } }, startRecognition() { // 检查权限 uni.getSetting({ success: (res) => { if (!res.authSetting['scope.record']) { uni.authorize({ scope: 'scope.record', success: () => { this.doRecognition() }, fail: () => { uni.showToast({ title: '需要麦克风权限才能识别', icon: 'none' }) } }) } else { this.doRecognition() } } }) }, doRecognition() { this.isRecognizing = true this.recognitionResult = '正在识别...' // 这里使用模拟数据,实际项目中需要调用语音识别 API setTimeout(() => { this.recognitionResult = '这是一段语音识别结果' this.isRecognizing = false }, 3000) }, stopRecognition() { this.isRecognizing = false this.recognitionResult = '识别已停止' }, initTTS() { this.ttsContext = uni.createInnerAudioContext() }, textToSpeech() { if (!this.recognitionResult) { uni.showToast({ title: '请先进行语音识别', icon: 'none' }) return } // 这里使用模拟数据,实际项目中需要调用文本转语音 API uni.showToast({ title: '正在播放语音', icon: 'none' }) // 模拟播放 setTimeout(() => { uni.showToast({ title: '语音播放完成', icon: 'none' }) }, 2000) } } } </script>
学习目标
- 掌握 uni-app 中音频处理的核心 API 和方法
- 学会实现音频录制、播放、编辑等功能
- 掌握音频优化的技巧和方法
- 了解跨平台音频处理的差异和解决方案
- 能够开发包含音频功能的完整应用
- 提升应用中音频的用户体验
总结
音频处理是 uni-app 应用开发中的重要组成部分,通过合理使用音频 API、优化音频参数、实现高效的音频管理,可以显著提升应用的用户体验。在实际开发中,开发者应该根据具体场景选择合适的音频格式和参数,平衡音质和文件大小,为用户提供最佳的音频体验。
同时,开发者还需要注意跨平台音频处理的差异,使用条件编译或平台特定的 API 来处理不同平台的音频问题,确保应用在所有平台上都能正常运行音频功能。对于需要高质量音频处理的应用,还可以考虑使用第三方音频处理库或云服务,提升音频处理能力。