HTML5采集录音与发送语音功能

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta
      name="viewport"
      content="width=device-width,height=device-height, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
    />
    <title>HTML5采集录音与发送语音功能</title>
  </head>
  <body>
    <div>
      <audio controls autoplay></audio>
      <input onclick="startRecording()" type="button" value="录音" />
      <input onclick="stopRecording()" type="button" value="停止" />
      <input onclick="playRecording()" type="button" value="播放" />
      <input onclick="uploadAudio()" type="button" value="提交" />
    </div>

    <script>
      //兼容
      window.URL = window.URL || window.webkitURL;
      navigator.getUserMedia =
        navigator.getUserMedia ||
        navigator.webkitGetUserMedia ||
        navigator.mozGetUserMedia ||
        navigator.msGetUserMedia;

      var AsaiRecorder = function (stream, config) {
        config = config || {};
        config.sampleBits = config.sampleBits || 16; //采样数位 8, 16
        config.sampleRate = config.sampleRate || 16000; //采样率(1/6 44100)

        var context = new AudioContext();
        var audioInput = context.createMediaStreamSource(stream);
        var recorder = context.createScriptProcessor(4096, 1, 1);

        var audioData = {
          size: 0, //录音文件长度
          buffer: [], //录音缓存
          inputSampleRate: context.sampleRate, //输入采样率
          inputSampleBits: 16, //输入采样数位 8, 16
          outputSampleRate: config.sampleRate, //输出采样率
          oututSampleBits: config.sampleBits, //输出采样数位 8, 16
          input: function (data) {
            this.buffer.push(new Float32Array(data));
            this.size += data.length;
          },
          compress: function () {
            //合并压缩
            //合并
            var data = new Float32Array(this.size);
            var offset = 0;
            for (var i = 0; i < this.buffer.length; i++) {
              data.set(this.buffer[i], offset);
              offset += this.buffer[i].length;
            }
            //压缩
            var compression = parseInt(
              this.inputSampleRate / this.outputSampleRate
            );
            var length = data.length / compression;
            var result = new Float32Array(length);
            var index = 0,
              j = 0;
            while (index < length) {
              result[index] = data[j];
              j += compression;
              index++;
            }
            return result;
          },
          encodeWAV: function () {
            var sampleRate = Math.min(
              this.inputSampleRate,
              this.outputSampleRate
            );
            var sampleBits = Math.min(
              this.inputSampleBits,
              this.oututSampleBits
            );
            var bytes = this.compress();
            var dataLength = bytes.length * (sampleBits / 8);
            var buffer = new ArrayBuffer(44 + dataLength);
            var data = new DataView(buffer);

            var channelCount = 1; //单声道
            var offset = 0;

            var 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, channelCount, true);
            offset += 2;
            // 采样率,每秒样本数,表示每个通道的播放速度
            data.setUint32(offset, sampleRate, true);
            offset += 4;
            // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
            data.setUint32(
              offset,
              channelCount * sampleRate * (sampleBits / 8),
              true
            );
            offset += 4;
            // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
            data.setUint16(offset, 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;
            // 写入采样数据
            if (sampleBits === 8) {
              for (var i = 0; i < bytes.length; i++, offset++) {
                var s = Math.max(-1, Math.min(1, bytes[i]));
                var val = s < 0 ? s * 0x8000 : s * 0x7fff;
                val = parseInt(255 / (65535 / (val + 32768)));
                data.setInt8(offset, val, true);
              }
            } else {
              for (var i = 0; i < bytes.length; i++, offset += 2) {
                var s = Math.max(-1, Math.min(1, bytes[i]));
                data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
              }
            }

            return new Blob([data], { type: "audio/wav" });
          },
        };

        //开始录音
        this.start = function () {
          audioInput.connect(recorder);
          recorder.connect(context.destination);
        };

        //停止
        this.stop = function () {
          recorder.disconnect();
        };

        //获取音频文件
        this.getBlob = function () {
          this.stop();
          return audioData.encodeWAV();
        };

        //回放
        this.play = function (audio) {
          audio.src = window.URL.createObjectURL(this.getBlob());
          console.log(666.1002, audio.src);
        };

        //上传
        this.upload = function (url, callback) {
          var fd = new FormData();
          fd.append("audioData", this.getBlob());
          console.log(666.1001, this.getBlob());
          var xhr = new XMLHttpRequest();
          if (callback) {
            xhr.upload.addEventListener(
              "progress",
              function (e) {
                callback("uploading", e);
              },
              false
            );
            xhr.addEventListener(
              "load",
              function (e) {
                callback("ok", e);
              },
              false
            );
            xhr.addEventListener(
              "error",
              function (e) {
                callback("error", e);
              },
              false
            );
            xhr.addEventListener(
              "abort",
              function (e) {
                callback("cancel", e);
              },
              false
            );
          }
          xhr.open("POST", url);
          xhr.send(fd);
        };

        //音频采集
        recorder.onaudioprocess = function (e) {
          audioData.input(e.inputBuffer.getChannelData(0));
          //record(e.inputBuffer.getChannelData(0));
        };
      };
      //抛出异常
      AsaiRecorder.throwError = function (message) {
        alert(message);
        throw new (function () {
          this.toString = function () {
            return message;
          };
        })();
      };
      //是否支持录音
      AsaiRecorder.canRecording = navigator.getUserMedia != null;
      //获取录音机
      AsaiRecorder.get = function (callback, config) {
        if (callback) {
          if (navigator.getUserMedia) {
            navigator.getUserMedia(
              { audio: true }, //只启用音频
              function (stream) {
                var rec = new AsaiRecorder(stream, config);
                callback(rec);
              },
              function (error) {
                console.log(error);
                switch (error.code || error.name) {
                  case "PERMISSION_DENIED":
                  case "PermissionDeniedError":
                    AsaiRecorder.throwError("用户拒绝提供信息。");
                    break;
                  case "NOT_SUPPORTED_ERROR":
                  case "NotSupportedError":
                    AsaiRecorder.throwError("浏览器不支持硬件设备。");
                    break;
                  case "MANDATORY_UNSATISFIED_ERROR":
                  case "MandatoryUnsatisfiedError":
                    AsaiRecorder.throwError("无法发现指定的硬件设备。");
                    break;
                  default:
                    AsaiRecorder.throwError(
                      "无法打开麦克风。异常信息:" + (error.code || error.name)
                    );
                    break;
                }
              }
            );
          } else {
            AsaiRecorder.throwErr("当前浏览器不支持录音功能。");
            return;
          }
        }
      };

      window.AsaiRecorder = AsaiRecorder;

      var recorder;
      var audio = document.querySelector("audio");
      function startRecording() {
        AsaiRecorder.get(function (rec) {
          recorder = rec;
          recorder.start();
        });
      }
      function stopRecording() {
        recorder.stop();
      }
      function playRecording() {
        recorder.play(audio);
      }
      function uploadAudio() {
        recorder.upload(
          "http://localhost:9098/****/UploadVideoServlet",
          function (state, e) {
            switch (state) {
              case "uploading":
                break;
              case "ok":
                alert("上传成功");
                break;
              case "error":
                alert("上传失败");
                break;
              case "cancel":
                alert("上传被取消");
                break;
            }
          }
        );
      }
    </script>
  </body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿赛工作室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值