1. Концепция
звук: Звук здесь относится к серии изменений напряжения, генерируемых микрофоном, которые могут иметь много значений между [-1,1]. Если вы хотите играть, вам нужно преобразовать в формат pcm
PCM: Формат pcm описывается тремя параметрами [частота дискретизации, количество бит дискретизации, количество каналов], найдите картинку в интернете:
Основная идея ПКМ разбивается на несколько небольших частей по горизонтали и вертикали, а затем точки по этим координатам используются для приблизительного описания волны (звука)
входная частота дискретизации: Относится к частоте, на которой микрофон улавливает звук, поскольку микрофону необходимо преобразовать звук формы волны в сигнал [-1,1], который используется для указания количества сэмплов, собираемых в единицу времени.
выходная частота дискретизации: Сколько сэмплов проигрывается в единицу времени, как правило, остается таким же, каквходная частота дискретизациипоследовательный
2. Практические сценарии
Давайте реализуем демонстрацию, откроем микрофон компьютера через браузер Google, используем API-интерфейс, связанный с webrtc, для записи, затем конвертируем его в формат pcm, wav и воспроизводим его с тегом audio, а также используем каваны для рисования карты звукового поля. Общий процесс выглядит следующим образом:
3. Этапы реализации
1. Получите разрешение на микрофон
Здесь используется метод navigator.getUserMedia.Конечно, если вы используете только Google Chrome, то можно обойтись и без обработки совместимости.Основной код структуры выглядит следующим образом
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia
navigator.getUserMedia({
audio: true // 这里面有video 和 audio 两个参数,视频选择video
}, (stream) => {
<!--这个stream 就是采集pcm数据的音源-->
}, (error) => {
console.log(error)
})
2, сбор данных ПКМ
Затем мы используем window.AudioContext для анализа информации о микрофоне, сосредоточившись на трех методах: createMediaStreamSource, createScriptProcessor и onaudioprocess.Конкретный код структуры выглядит следующим образом.
<!--首先new一个AudioContext对象,作为声源的载体 -->
let audioContext = window.AudioContext || window.webkitAudioContext
const context = new audioContext()
<!--将声音输入这个对像,stream 就是上面返回音源-->
let audioInput = context.createMediaStreamSource(stream)
<!--创建声音的缓存节点,createScriptProcessor方法的第二个和第三个参数指的是输入和输出都是声道数,第一个参数缓存大小,一般数值为1024,2048,4096,这里选用4096-->
let recorder = context.createScriptProcessor(config.bufferSize, config.channelCount, config.channelCount) // 这里config是自定义,后面会附带源码
<!--此方法音频缓存,这里audioData是自定义对象,这个对象会实现wav文件转换,缓存pcm数据等-->
recorder.onaudioprocess = (e) => {
audioData.input(e.inputBuffer.getChannelData(0))
}
Однако в процессе приобретения должна быть триггерная точка.Например, окончательный рендеринг демонстрации этой практики выглядит следующим образом:
Итак, в процессе записи, по нажатию кнопки записи на картинке gif, следующие две строки кода срабатывают по событию клика (onclick).Если код не срабатывает при клике (или другом событии), то onaudioprocess метод не получит информацию об источнике звука, которую вы записали после включения разрешения микрофона
audioInput.connect(recorder) //声音源链接过滤处理器
recorder.connect(context.destination) //过滤处理器链接扬声器
После создания ссылки метод onaudioprocess класса createScriptProcessor может непрерывно возвращать выборочные данные, диапазон данных находится в диапазоне [-1, 1] и имеет тип Float32. Все, что нам нужно сделать сейчас, это собрать их и превратить в данные файла PCM.
3. Определение аудиоданных
Сначала определите объект audioData для обработки данных.Общая структура выглядит следующим образом, подробности см. в следующем исходном коде:
let audioData = {
size: 0, //录音文件长度
buffer: [], //录音缓存
inputSampleRate: context.sampleRate, //输入采样率
inputSampleBits: 16, //输入采样数位 8, 16
outputSampleRate: config.sampleRate, //输出采样率
oututSampleBits: config.sampleBits, //输出采样数位 8, 16
input: function(data) { // 实时存储录音的数据
},
getRawData: function() { //合并压缩
},
covertWav: function() { // 转换成wav文件数据
},
getFullWavData: function() { // 用blob生成文件
},
closeContext: function(){ //关闭AudioContext否则录音多次会报错
},
reshapeWavData: function(sampleBits, offset, iBytes, oData) { // 8位采样数位
},
getWavBuffer: function() { // 用于绘图wav格式的buffer数据
},
getPcmBuffer: function() { // pcm buffer 数据
}
}
Судя по гифке выше:
А. Первый шаг — нажать «Запись», чтобы выполнить главу.1. Получите разрешение на микрофона также2, сбор данных ПКМсоответствующий процесс, Затем вызывается метод ввода объекта audioData в методе onaudioprocess для сохранения данных буфера;
б) Перейдите на вкладку "Скачать pcm", и методы getRawData и getPcmBuffer объекта audioData будут выполняться по очереди, но загруженный файл txt не является файлом pcm.Поскольку я не знаю, как преобразовать файл txt в файл pcm в среде js, который я использую После загрузки файла txt имя расширения изменяется вручную.Конечно, измененный файл можно воспроизвести.Процесс работы выглядит следующим образом
4, пкм в wav
pcm не имеет информации заголовка, если добавить 44 байта информации заголовка, его можно преобразовать в wav. Информация заголовка фиксирована и может использоваться напрямую. Заимствуйте те же фрагменты кода из Интернета.
let writeString = function (str) {
for (var i = 0; i < str.length; i++) {
data.setUint8(offset + i, str.charCodeAt(i))
}
}
// 资源交换文件标识符
writeString('RIFF'); offset += 4
// 下个地址开始到文件尾总字节数,即文件大小-8
data.setUint32(offset, 36 + dataLength, true); offset += 4
// WAV文件标志
writeString('WAVE'); offset += 4
// 波形格式标志
writeString('fmt '); offset += 4
// 过滤字节,一般为 0x10 = 16
data.setUint32(offset, 16, true); offset += 4
// 格式类别 (PCM形式采样数据)
data.setUint16(offset, 1, true); offset += 2
// 通道数
data.setUint16(offset, config.channelCount, true); offset += 2
// 采样率,每秒样本数,表示每个通道的播放速度
data.setUint32(offset, sampleRate, true); offset += 4
// 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
data.setUint32(offset, config.channelCount * sampleRate * (sampleBits / 8), true); offset += 4
// 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
data.setUint16(offset, config.channelCount * (sampleBits / 8), true); offset += 2
// 每样本数据位数
data.setUint16(offset, sampleBits, true); offset += 2
// 数据标识符
writeString('data'); offset += 4
// 采样数据总数,即数据总大小-44
data.setUint32(offset, dataLength, true); offset += 4
// 写入采样数据
data = this.reshapeWavData(sampleBits, offset, bytes, data)
5. Данные для карты звукового поля
Преобразование в карту звукового поля фокусируется на методе createAnalyser в AudioContext, который может разлагать звуковые волны. Конкретные шаги заключаются в следующем:
window.audioBufferSouceNode = context.createBufferSource() //创建声源对象
audioBufferSouceNode.buffer = buffer /声源buffer文件流
gainNode = context.createGain() //创建音量控制器
gainNode.gain.value = 2
audioBufferSouceNode.connect(gainNode) //声源链接音量控制器
let analyser = context.createAnalyser() //创建分析器
analyser.fftSize = 256
gainNode.connect(analyser) //音量控制器链接分析器
analyser.connect(context.destination) //分析器链接扬声器
Затем используйте данные analyser.frequencyBinCount для рисования с помощью холста, основной код выглядит следующим образом:
let drawing = function() {
let array = new Uint8Array(analyser.frequencyBinCount)
analyser.getByteFrequencyData(array)
ctx.clearRect(0, 0, 600, 200)
for(let i = 0; i < array.length; i++) {
let _height = array[i]
if(!top[i] || (_height > top[i])) {//帽头落下
top[i] = _height
} else {
top[i] -= 1
}
ctx.fillRect(i * 20, 200 - _height, 4, _height)
ctx.fillRect(i * 20, 200 - top[i] -6.6, 4, 3.3)//绘制帽头
ctx.fillStyle = gradient
}
requestAnimationFrame(drawing)
}
4. Адрес источника
Адрес исходного кода на гитхабе:audio