IESM项目实训四——Web Audio录音和字符串转拼音

IESM项目实训四

通过浏览器录制音频,生成可供百度语音识别api使用的pcm音频文件,然后将音频数据传至后端。根据百度语音识别api文档要求,音频文件为pcm格式,单音道,16k采样率,16位深。

1.三个基本概念

音频源,也就是音频输入,可以是直接从设备输入的音频,也可以是远程获取的音频文件。
处理节点,分析器和处理器,比如音调节点,音量节点,声音处理节点。
输出源,指音频渲染设备,一般情况下是用户设备的扬声器,即context.destination。其实,音频源和输出源也都可以视为节点,这三者的关系可以用这张图表示:
在这里插入图片描述

2.使用Web Audio进行录音

录音过程如下:

  1. 输入源 getUserMedia()
    首先调用用户的麦克风设备,需要用到navigator.mediaDevices.getUserMedia()
    这个方法,该方法会返回一个promise,成功回调的参数是一个MediaStream对象,该对象就是麦克风采集的语音。
    然后该MediaStream流媒体对象需要传入createMediaStreamSource()方法中,用于创建一个新的MediaStreamAudioSourceNode对象,也就是创建了一个输入源节点,从而使得来自MediaStream的音频可以被播放和操作。
navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) { // 务必分开写then
  var audioContext = new AudioContext()
  var sourceNode = audioContext.createMediaStreamSource(stream) //sourceNode就是得到的输入源节点
  })

2.处理节点createScriptProcessor()
createScriptProcessor()方法用于创建一个处理节点,该处理节点有三个参数:

  1. bufferSize,缓冲区大小,以样本帧为单位。一般有以下值 256,512,1024,2048,4096,8192,16384。当传0时,系统会取当前环境最合适的缓冲区大小。每当缓冲区满时,则会触发audioprocess事件,即bufferSize控制着回调事件的频率。注:mdn提示 chrome 31版本的不支持传0的方式。
  2. numberOfInputChannels,值为整数,用于指定输入node的声道的数量,默认值是2,最高能为32,且不能为0。
  3. numberOfOutputChannels,值为整数,用于指定输出node的声道的数量,默认值是2,最高能取32,不能为0。
    由于百度语音识别api要求使用单音道,且采样率为16k,位深为16bit;而实际麦克风的录音的采样率为44.1k,双音道,故需要对输入源的音频进行处理,即通过监听onaudioprocess即可对音频信号进行处理。
var scriptNode = audioContext.createScriptProcessor(4096, 1, 1)
scriptNode.onaudioprocess = event => {
audioData.input(event.inputBuffer.getChannelData(0)) //取单音道信号
}
//对音频信号进行处理
var audioData = {
  size: 0,
  buffer: [],
  input: function (data) {
    this.buffer.push(new Float32Array((data)))
    this.size += data.length
  },
  //得到格式为pcm,采样率为16k,位深为16bit的音频文件
  getData: function () {
    var sampleBits = 16
    var inputSampleRate = 44100
    var outputSampleRate = 16000
    var bytes = this.decompress(this.buffer, this.size, inputSampleRate, outputSampleRate)
    var dataLen = bytes.length * (sampleBits / 8)
    var buffer = new ArrayBuffer(dataLen) // For PCM , 浏览器无法播放pcm格式音频
    var data = new DataView(buffer)
    var offset = 0
    data = this.reshapeData(sampleBits, offset, bytes, data)   
    return new Blob([data], { type: 'audio/pcm' })
  },
  // 将收到的音频信号进行预处理,即将二维数组转成一维数组,并且对音频信号进行降采样
  decompress: function (buffer, size, inputSampleRate, outputSampleRate) {
    var data = new Float32Array(size)
    var offset = 0
    for (var i = 0; i < buffer.length; i++) {
      data.set(buffer[i], offset)
      offset += buffer[i].length
    }
    // 降采样,采取每interval长度取一个信号点的方式
    var interval = parseInt(inputSampleRate / outputSampleRate)
    var length = data.length / interval
    var result = new Float32Array(length)
    var index = 0; var j = 0
    while (index < length) {
      result[index] = data[j]
      j += interval
      index++
    }
    return result
  },
  //将音频信号转为16bit位深
  reshapeData: function (sampleBits, offset, bytes, data) {
    var s
    for (var i = 0; i < bytes.length; i++, offset += (sampleBits / 8)) {
      s = Math.max(-1, Math.min(1, bytes[i]))
      data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true)
    }
    return data
  }

3.输出源audioContext.destination
以上三个节点Source Node,scriptNode 和 audioContext.destination通过connect相连。

sourceNode.connect(scriptNode) //输入源节点连接到处理节点
scriptNode.connect(audioContext.destination) //处理节点连接到输出源节点

3.完整代码如下

recorder.js

import request from "request";

function Recorder (stream) {
  var audioContext = new AudioContext()
  var sourceNode = audioContext.createMediaStreamSource(stream)
  var scriptNode = audioContext.createScriptProcessor(4096, 1, 1)
  var audioData = {
    size: 0,
    buffer: [],
    input: function (data) {
      this.buffer.push(new Float32Array((data)))
      this.size += data.length
    },
    getData: function () {
      var sampleBits = 16
      var inputSampleRate = 44100
      var outputSampleRate = 16000
      var bytes = this.decompress(this.buffer, this.size, inputSampleRate, outputSampleRate)
      var dataLen = bytes.length * (sampleBits / 8)
      var buffer = new ArrayBuffer(dataLen) // For PCM , 浏览器无法播放pcm格式音频
      var data = new DataView(buffer)
      var offset = 0
      data = this.reshapeData(sampleBits, offset, bytes, data)
      return new Blob([data], { type: 'audio/pcm' })
    },
    // 将二维数组转成一维数组
    decompress: function (buffer, size, inputSampleRate, outputSampleRate) {
      var data = new Float32Array(size)
      var offset = 0
      for (var i = 0; i < buffer.length; i++) {
        data.set(buffer[i], offset)
        offset += buffer[i].length
      }
      // 降采样
      var interval = parseInt(inputSampleRate / outputSampleRate)
      var length = data.length / interval
      var result = new Float32Array(length)
      var index = 0; var j = 0
      while (index < length) {
        result[index] = data[j]
        j += interval
        index++
      }
      return result
    },
    reshapeData: function (sampleBits, offset, bytes, data) {
      var s
      for (var i = 0; i < bytes.length; i++, offset += (sampleBits / 8)) {
        s = Math.max(-1, Math.min(1, bytes[i]))
        data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true)
      }
      return data
    }
  }
  // 监听录音的过程
  var buffers = []
  scriptNode.onaudioprocess = event => {
    audioData.input(event.inputBuffer.getChannelData(0))
  }
  this.start = function () {
    sourceNode.connect(scriptNode)
    scriptNode.connect(audioContext.destination)
  }
  this.stop = function () {
    sourceNode.disconnect()
    scriptNode.disconnect()  }
  this.getBlob = function () {
    return audioData.getData()
  }
  this.clear = function() {
    audioData.clear();
  }
}
Recorder.get = function (callback) {
  navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) { // 务必分开写then
    var rec = new Recorder(stream)
    callback(rec)
  })
}
export { Recorder }
export function remoteControl(num,roId) {
  return request({
    url:'/ScoresInput/scores/voiceInputScores',
    method: 'post',
    params: {
      num,
      roId
    }
  })
}

ScoreInputList.vue成绩录入界面使用到音频录制,后续需要进行修改

//开始录制方法,如需开始录制音频,可如下
 startRecording: function () {
        Recorder.get(function (rec) {
        recorder = rec
        recorder.start()
      })
    },
 //停止音频录制
 stopRecording: function () {
      recorder.stop();
      console.log('已暂停');
    },
    //前期测试录制的音频文件是否正确
    getRecording: function () {
      var voiceBlobFile = recorder.getBlob();  //得到需要的pcm文件
      saveAs(voiceBlobFile, "test8.pcm");//存入本地
      console.log(voiceBlobFile);
    },
    beforeDestroy:function () {
      this.$once('hook:beforeDestroy', () => {
        if (recorder !== null) {
          recorder.stop();
          recorder = null;
        }
      })
    }

考虑将名字转换为拼音

因为名字不算日常用语,百度语音识别结果不能与真实的姓名一致,但是会发音一致,为了提高准确率,将识别的姓名转为拼音再进行对比。
需要添加依赖:

<!--拼音依赖-->
		<dependency>
			<groupId>com.belerweb</groupId>
			<artifactId>pinyin4j</artifactId>
			<version>2.5.0</version>
		</dependency>

工具类:
将字符串转为拼音

import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;

public class pingyin {
    public static String getPinyin(String src) {
        char[] t1 = null;
        t1 = src.toCharArray();
        String[] t2 = new String[t1.length];
        HanyuPinyinOutputFormat t3 = new HanyuPinyinOutputFormat();
        t3.setCaseType(HanyuPinyinCaseType.LOWERCASE);// 小写格式
        t3.setToneType(HanyuPinyinToneType.WITHOUT_TONE);// 有无音标
        t3.setVCharType(HanyuPinyinVCharType.WITH_V);
        String t4 = "";
        try {
            for (int i = 0; i < t1.length; i++) {
                // 判断是否为汉字字符
                // if(t1[i] >= 32 && t1[i] <= 125)//ASCII码表范围内直接返回
                if (String.valueOf(t1[i]).matches("[\\u4E00-\\u9FA5]+")) {
                    t2 = PinyinHelper.toHanyuPinyinStringArray(t1[i], t3);// 转化为拼音
                    t4 += t2[0].substring(0, 1).toUpperCase() + t2[0].substring(1);// 首字母大写
                } else {
                    t4 += String.valueOf(t1[i]);// 不是汉字不处理
                }
            }
        } catch (BadHanyuPinyinOutputFormatCombination e1) {
            e1.printStackTrace();
        }
        return t4;
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值