<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 实现录音,并压缩的更多相关文章

  1. 【HTML5】Web Audio API打造超炫的音乐可视化效果

    HTML5真是太多炫酷的东西了,其中Web Audio API算一个,琢磨着弄了个音乐可视化的demo,先上效果图: 项目演示:别说话,点我!  源码已经挂到github上了,有兴趣的同学也可以去st ...

  2. H5的Web Audio Api

    概述 研究Web Audio Api的主要原因是:工作中需要在ios中实现声音的淡出效果,主要是通过setInterval来改audio标签的volume属性实现的,但是ios上面volume属性是只 ...

  3. Web Audio API之手把手教你用web api处理声音信号:可视化音乐demo

    1.Web Audio API 介绍 Web Audio API 提供了在Web上控制音频的一个非常有效通用的系统 ,这些通用系统通俗的讲就是我们可以利用Web Audio API提供的各种方法操作各 ...

  4. 关于HTML5音频——audio标签和Web Audio API各平台浏览器的支持情况

    对比audio标签 和 Web Audio API 各平台浏览器的支持情况:   audio element Web Audio API desktop browsers Chrome 14 Yes  ...

  5. [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 ...

  6. 关于Web Audio API的入门

    Web Audio API提供了一个简单强大的机制来实现控制web应用程序的音频内容.它允许你开发复杂的混音,音效,平移以及更多. 可以先看一下MDN的这篇文章<Web Audio API的运用 ...

  7. 使用Web Audio API绘制音波图

    摘要:Web Audio API是对<audio> 标签功能上的补充,我们可以用它完成混音.音效.平移等各种复杂的音频处理,本文简单的使用其完成音波图的绘制. PS:本例子使用ES6编程, ...

  8. HTML5 ——web audio API 音乐可视化(二)

    上一篇 web audio API 音乐可视化(一)介绍了一些基本的API,以及如何简单的播放一个音频,本篇介绍一下怎么对获取到的音频进行分析,并将分析后的数据绘制成图像. 最终效果请戳这里; 完整版 ...

  9. HTML5 ——web audio API 音乐可视化(一)

    使用Web Audio API可以对音频进行分析和操作,最终实现一个音频可视化程序. 最终效果请戳这里; 完整版代码请戳这里,如果还看得过眼,请给一个start⭐ 一.API AudioContext ...

随机推荐

  1. 关于JS读取DOM对象(标签)的自定义属性

    DOM对象对于js来说,是个很基础的元素,我们写js一般来说,都一定会对它进行操作.我们可以很方便地给它加上自定义的属性,比如: var test = document.getElementById( ...

  2. inode的若干锚

    /** * __insert_inode_hash - hash an inode * @inode: unhashed inode * @hashval: unsigned long value u ...

  3. dubbo远程服务调用和maven依赖的区别

    dubbo:跨系统通信.比如:两个系统,一个系统A作客户端,一个系统B作服务器, 服务器B把自己的接口定义提供给客户端A,客户端A将接口定义在spring中的bean.客户端A可直接使用这个bean, ...

  4. Kindeditor在线文本编辑器过滤HTML

    KindEditor.ready(function (K) { editor = K.create('textarea[name="content"]', { filterMode ...

  5. 利用程序随机构造N个已解答的数独棋盘

    高级软件工程第二次作业:利用程序随机构造N个已解答的数独棋盘,代码如下: package SudokuGame; /** * 解决这个问题使用的是回溯+剪枝的算法 * 基本思想:不断地将每个格子可填入 ...

  6. luogu P4183 Cow at Large P (暴力吊打点分治)(内有时间复杂度证明)

    题面 贝茜被农民们逼进了一个偏僻的农场.农场可视为一棵有N个结点的树,结点分别编号为 1,2,-,N .每个叶子结点都是出入口.开始时,每个出入口都可以放一个农民(也可以不放).每个时刻,贝茜和农民都 ...

  7. 字典dict详解

    字典也是 Python 提供的一种常用的数据结构,它用于存放具有映射关系的数据. 比如有份成绩表数据,语文:79,数学:80,英语:92,这组数据看上去像两个列表,但这两个列表的元素之间有一定的关联关 ...

  8. 修改el-input

    无论是input框,还是select, 都是对 el-input 进行修改: .houseRegisterCompany .el-input{ width: 130px; }

  9. 34.Merge Intervals(合并序列)

    Level:   Medium 题目描述: Given a collection of intervals, merge all overlapping intervals. Example 1: I ...

  10. top查看进程的参数

    top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器. top显示系统当前的进程和其他状况,是一个动态显示过程,即可以通过用户按键来不 ...