Web RTC + audio API 实现录音,并压缩
<button onclick="record()">开始录音</button>
<button onclick="stopRecord()">结束录音</button>
<!-- <button onclick="resetRecord()">重置录音</button> -->
<audio class="audio-node" id="audio" autoplay></audio>
<script>
class Recoder {
constructor (sampleRate) {
this.leftDataList = []
this.rightDataList = []
this.mediaPlayer = null
this.audioContext = null
this.source = null
this.sampleRate = sampleRate || 44100
}
startRecord () {
return new Promise((resolve, reject) => {
window.navigator.mediaDevices.getUserMedia({
audio: {
sampleRate: 8000, // 采样率
channelCount: 1, // 声道
audioBitsPerSecond : 64,
volume: 1.0, // 音量
autoGainControl: true
}
}).then(mediaStream => {
console.log(mediaStream,'mediaStream')
this.mediaPlayer = mediaStream
this.beginRecord(mediaStream)
resolve()
}).catch(err => {
// 如果用户电脑没有麦克风设备或者用户拒绝了,或者连接出问题了等
// 这里都会抛异常,并且通过err.name可以知道是哪种类型的错误
console.error(err)
reject(err)
})
})
} beginRecord (mediaStream) {
let audioContext = new (window.AudioContext || window.webkitAudioContext)()
// mediaNode包含 mediaStream,audioContext
let mediaNode = audioContext.createMediaStreamSource(mediaStream)
console.log(mediaNode,'mediaNode')
// 创建一个jsNode
// audioContext.sampleRate = 8000
console.log(audioContext,'audioContext')
let jsNode = this.createJSNode(audioContext)
console.log(jsNode,'jsnode')
// 需要连到扬声器消费掉outputBuffer,process回调才能触发
// 并且由于不给outputBuffer设置内容,所以扬声器不会播放出声音
jsNode.connect(audioContext.destination)
jsNode.onaudioprocess = this.onAudioProcess.bind(this)
// 把mediaNode连接到jsNode
mediaNode.connect(jsNode)
this.audioContext = audioContext
} onAudioProcess (event) {
console.log('is recording')
// 拿到输入buffer Float32Array
let audioBuffer = event.inputBuffer
let leftChannelData = audioBuffer.getChannelData(0)
// let rightChannelData = audioBuffer.getChannelData(1) // 需要克隆一下
this.leftDataList.push(leftChannelData.slice(0))
//this.rightDataList.push(rightChannelData.slice(0))
} createJSNode (audioContext) {
const BUFFER_SIZE = 4096
const INPUT_CHANNEL_COUNT = 1
const OUTPUT_CHANNEL_COUNT = 1
// createJavaScriptNode已被废弃
let creator = audioContext.createScriptProcessor || audioContext.createJavaScriptNode
creator = creator.bind(audioContext)
return creator(BUFFER_SIZE, INPUT_CHANNEL_COUNT, OUTPUT_CHANNEL_COUNT)
} playRecord (arrayBuffer) {
let blob = new Blob([new Int8Array(arrayBuffer)], {
type: 'audio/mp3' // files[0].type
})
let blobUrl = URL.createObjectURL(blob)
this.source = blob
this.blobUrl = blobUrl
// document.querySelector('.audio-node').src = blobUrl
return blobUrl
} stopRecord () {
// 停止录音
let leftData = this.mergeArray(this.leftDataList)
//let rightData = this.mergeArray(this.rightDataList)
let allData = this.interSingleData(leftData)
let wavBuffer = this.createWavFile(allData) let source = this.playRecord(wavBuffer)
this.resetRecord()
return source
} transformArrayBufferToBase64 (buffer) {
var binary = ''
var bytes = new Uint8Array(buffer)
for (var len = bytes.byteLength, i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i])
}
return window.btoa(binary)
} // 停止控件录音
resetRecord () {
this.leftDataList = []
this.rightDataList = []
this.audioContext.close()
this.mediaPlayer.getAudioTracks().forEach(track => {
track.stop()
this.mediaPlayer.removeTrack(track)
})
} createWavFile (audioData) {
let channelCount = 1
const WAV_HEAD_SIZE = 44
const sampleBits = 16
let sampleRate = this.sampleRate let buffer = new ArrayBuffer(audioData.length * 2 + WAV_HEAD_SIZE)
// 需要用一个view来操控buffer
let view = new DataView(buffer)
// 写入wav头部信息
// RIFF chunk descriptor/identifier
this.writeUTFBytes(view, 0, 'RIFF')
// RIFF chunk length
view.setUint32(4, 44 + audioData.length * channelCount, true)
// RIFF type
this.writeUTFBytes(view, 8, 'WAVE')
// format chunk identifier
// FMT sub-chunk
this.writeUTFBytes(view, 12, 'fmt ')
// format chunk length
view.setUint32(16, 16, true)
// sample format (raw)
view.setUint16(20, 1, true)
// stereo (2 channels)
view.setUint16(22, channelCount, true)
// sample rate
view.setUint32(24, sampleRate , true)
// byte rate (sample rate * block align)
view.setUint32(28, sampleRate * 2, true)
// block align (channel count * bytes per sample)
view.setUint16(32, 2 * 2, true)
// bits per sample
view.setUint16(34, 16, true)
// data sub-chunk
// data chunk identifier
this.writeUTFBytes(view, 36, 'data')
// data chunk length
view.setUint32(40, audioData.length * 2, true) console.log(view,'view')
let length = audioData.length
let index = 44
let volume = 1
for (let i = 0; i < length; i++) {
view.setInt16(index, audioData[i] * (0x7FFF * volume), true)
index += 2
}
return buffer
} writeUTFBytes (view, offset, string) {
var lng = string.length
for (var i = 0; i < lng; i++) {
view.setUint8(offset + i, string.charCodeAt(i))
}
} interSingleData (left) {
var t = left.length;
let sampleRate = this.audioContext.sampleRate,
outputSampleRate = this.sampleRate
sampleRate += 0.0;
outputSampleRate += 0.0;
var s = 0,
o = sampleRate / outputSampleRate,
u = Math.ceil(t * outputSampleRate / sampleRate),
a = new Float32Array(u);
for (let i = 0; i < u; i++) {
a[i] = left[Math.floor(s)];
s += o;
}
return a;
} // 交叉合并左右声道的数据
interleaveLeftAndRight (left, right) {
let totalLength = left.length + right.length
let data = new Float32Array(totalLength)
for (let i = 0; i < left.length; i++) {
let k = i * 2
data[k] = left[i]
data[k + 1] = right[i]
}
return data
} mergeArray (list) {
let length = list.length * list[0].length
let data = new Float32Array(length)
let offset = 0
for (let i = 0; i < list.length; i++) {
data.set(list[i], offset)
offset += list[i].length
}
return data
} // 播放音乐
playMusic () {
if (!this.value) {
return
}
// 直接使用File对象生成blob url
let blobUrl = URL.createObjectURL(this.files[0])
document.querySelector('.audio-node').src = blobUrl
} play (arrayBuffer) {
// Safari需要使用webkit前缀
let AudioContext = this.AudioContext || this.webkitAudioContext
let audioContext = new AudioContext()
// 创建一个AudioBufferSourceNode对象,使用AudioContext的工厂函数创建
let audioNode = audioContext.createBufferSource()
// 解码音频,可以使用Promise,但是较老的Safari需要使用回调
audioContext.decodeAudioData(arrayBuffer, function (audioBuffer) {
audioNode.buffer = audioBuffer
audioNode.connect(audioContext.destination)
// 从0s开始播放
audioNode.start(0)
})
}
}
let recoder = new Recoder(8000)
function record() {
recoder.startRecord()
} function stopRecord(params) {
recoder.stopRecord()
let source = recoder.source
let formData = new FormData()
formData.append('audio', source)
let audio = document.getElementById('audio')
audio.src = recoder.blobUrl
}
</script>
参考 自掘金
1.网上很多都没有停之功能,新增停之
2.这里用的8000采样率和单声道,音频体积为原来的 快 1/12 (我电脑设备的采样录是44K)
3.详细解释请看 H5音频分析
Web RTC + audio API 实现录音,并压缩的更多相关文章
- 【HTML5】Web Audio API打造超炫的音乐可视化效果
HTML5真是太多炫酷的东西了,其中Web Audio API算一个,琢磨着弄了个音乐可视化的demo,先上效果图: 项目演示:别说话,点我! 源码已经挂到github上了,有兴趣的同学也可以去st ...
- H5的Web Audio Api
概述 研究Web Audio Api的主要原因是:工作中需要在ios中实现声音的淡出效果,主要是通过setInterval来改audio标签的volume属性实现的,但是ios上面volume属性是只 ...
- Web Audio API之手把手教你用web api处理声音信号:可视化音乐demo
1.Web Audio API 介绍 Web Audio API 提供了在Web上控制音频的一个非常有效通用的系统 ,这些通用系统通俗的讲就是我们可以利用Web Audio API提供的各种方法操作各 ...
- 关于HTML5音频——audio标签和Web Audio API各平台浏览器的支持情况
对比audio标签 和 Web Audio API 各平台浏览器的支持情况: audio element Web Audio API desktop browsers Chrome 14 Yes ...
- [Javascript] Intro to the Web Audio API
An introduction to the Web Audio API. In this lesson, we cover creating an audio context and an osci ...
- 关于Web Audio API的入门
Web Audio API提供了一个简单强大的机制来实现控制web应用程序的音频内容.它允许你开发复杂的混音,音效,平移以及更多. 可以先看一下MDN的这篇文章<Web Audio API的运用 ...
- 使用Web Audio API绘制音波图
摘要:Web Audio API是对<audio> 标签功能上的补充,我们可以用它完成混音.音效.平移等各种复杂的音频处理,本文简单的使用其完成音波图的绘制. PS:本例子使用ES6编程, ...
- HTML5 ——web audio API 音乐可视化(二)
上一篇 web audio API 音乐可视化(一)介绍了一些基本的API,以及如何简单的播放一个音频,本篇介绍一下怎么对获取到的音频进行分析,并将分析后的数据绘制成图像. 最终效果请戳这里; 完整版 ...
- HTML5 ——web audio API 音乐可视化(一)
使用Web Audio API可以对音频进行分析和操作,最终实现一个音频可视化程序. 最终效果请戳这里; 完整版代码请戳这里,如果还看得过眼,请给一个start⭐ 一.API AudioContext ...
随机推荐
- 关于JS读取DOM对象(标签)的自定义属性
DOM对象对于js来说,是个很基础的元素,我们写js一般来说,都一定会对它进行操作.我们可以很方便地给它加上自定义的属性,比如: var test = document.getElementById( ...
- inode的若干锚
/** * __insert_inode_hash - hash an inode * @inode: unhashed inode * @hashval: unsigned long value u ...
- dubbo远程服务调用和maven依赖的区别
dubbo:跨系统通信.比如:两个系统,一个系统A作客户端,一个系统B作服务器, 服务器B把自己的接口定义提供给客户端A,客户端A将接口定义在spring中的bean.客户端A可直接使用这个bean, ...
- Kindeditor在线文本编辑器过滤HTML
KindEditor.ready(function (K) { editor = K.create('textarea[name="content"]', { filterMode ...
- 利用程序随机构造N个已解答的数独棋盘
高级软件工程第二次作业:利用程序随机构造N个已解答的数独棋盘,代码如下: package SudokuGame; /** * 解决这个问题使用的是回溯+剪枝的算法 * 基本思想:不断地将每个格子可填入 ...
- luogu P4183 Cow at Large P (暴力吊打点分治)(内有时间复杂度证明)
题面 贝茜被农民们逼进了一个偏僻的农场.农场可视为一棵有N个结点的树,结点分别编号为 1,2,-,N .每个叶子结点都是出入口.开始时,每个出入口都可以放一个农民(也可以不放).每个时刻,贝茜和农民都 ...
- 字典dict详解
字典也是 Python 提供的一种常用的数据结构,它用于存放具有映射关系的数据. 比如有份成绩表数据,语文:79,数学:80,英语:92,这组数据看上去像两个列表,但这两个列表的元素之间有一定的关联关 ...
- 修改el-input
无论是input框,还是select, 都是对 el-input 进行修改: .houseRegisterCompany .el-input{ width: 130px; }
- 34.Merge Intervals(合并序列)
Level: Medium 题目描述: Given a collection of intervals, merge all overlapping intervals. Example 1: I ...
- top查看进程的参数
top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器. top显示系统当前的进程和其他状况,是一个动态显示过程,即可以通过用户按键来不 ...