千家信息网

html5录音功能如何实现

发表于:2024-10-16 作者:千家信息网编辑
千家信息网最后更新 2024年10月16日,这篇文章主要介绍"html5录音功能如何实现",在日常操作中,相信很多人在html5录音功能如何实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"html5录音功能如何
千家信息网最后更新 2024年10月16日html5录音功能如何实现

这篇文章主要介绍"html5录音功能如何实现",在日常操作中,相信很多人在html5录音功能如何实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"html5录音功能如何实现"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

缘起

由于项目需要,我们要在web端实现录音功能。一开始,找到的方案有两个,一个是通过iframe,一个是html5的getUserMedia api。由于我们的录音功能不需要兼容IE浏览器,所以毫不犹豫的选择了html5提供的getUserMedia去实现。基本思路是参考了官方的api文档以及网上查找的一些方案做结合做出了适合项目需要的方案。但由于我们必须保证这个录音功能能够同时在pad端、pc端都可以打开,所以其中也踩了一些坑。以下为过程还原。

步骤1

由于新的api是通过navigator.mediaDevices.getUserMedia,且返回一个promise。

而旧的api是navigator.getUserMedia,于是做了一个兼容性。代码如下:

// 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象if (navigator.mediaDevices === undefined) {    navigator.mediaDevices = {};}// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia// 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。if (navigator.mediaDevices.getUserMedia === undefined) {    let getUserMedia =        navigator.getUserMedia ||        navigator.webkitGetUserMedia ||        navigator.mozGetUserMedia ||        navigator.msGetUserMedia;    navigator.mediaDevices.getUserMedia = function(constraints) {        // 首先,如果有getUserMedia的话,就获得它        // 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口        if (!getUserMedia) {            return Promise.reject(new Error('getUserMedia is not implemented in this browser'));        }        // 否则,为老的navigator.getUserMedia方法包裹一个Promise        return new Promise(function(resolve, reject) {            getUserMedia.call(navigator, constraints, resolve, reject);        });    };

步骤2

这是网上存在的一个方法,封装了一个HZRecorder。基本上引用了这个方法。调用HZRecorder.get就可以调起录音接口,这个方法传入一个callback函数,new HZRecorder后执行callback函数且传入一个实体化后的HZRecorder对象。可以通过该对象的方法实现开始录音、暂停、停止、播放等功能。

var HZRecorder = function (stream, config) {      config = config || {};      config.sampleBits = config.sampleBits || 8;      //采样数位 8, 16      config.sampleRate = config.sampleRate || (44100 / 6);   //采样率(1/6 44100)            //创建一个音频环境对象      audioContext = window.AudioContext || window.webkitAudioContext;      var context = new audioContext();      //将声音输入这个对像      var audioInput = context.createMediaStreamSource(stream);            //设置音量节点      var volume = context.createGain();      audioInput.connect(volume);      //创建缓存,用来缓存声音      var bufferSize = 4096;      // 创建声音的缓存节点,createScriptProcessor方法的      // 第二个和第三个参数指的是输入和输出都是双声道。      var recorder = context.createScriptProcessor(bufferSize, 2, 2);      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.end = function() {        context.close();    };        // 继续    this.again = function() {        recorder.connect(context.destination);    };    //获取音频文件      this.getBlob = function () {          this.stop();          return audioData.encodeWAV();      };      //回放      this.play = function (audio) {          audio.src = window.URL.createObjectURL(this.getBlob());      };      //上传      this.upload = function (url, callback) {          var fd = new FormData();          fd.append('audioData', 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));      };  };  //抛出异常  HZRecorder.throwError = function (message) {      throw new function () { this.toString = function () { return message; };};  };  //是否支持录音  HZRecorder.canRecording = (navigator.getUserMedia != null);  //获取录音机  HZRecorder.get = function (callback, config) {     if (callback) {        navigator.mediaDevices            .getUserMedia({ audio: true })            .then(function(stream) {                let rec = new HZRecorder(stream, config);                callback(rec);            })            .catch(function(error) {                HZRecorder.throwError('无法录音,请检查设备状态');            });    }};  window.HZRecorder = HZRecorder;

以上,已经可以满足大部分的需求。但是我们要兼容pad端。我们的pad有几个问题必须解决。

  • 录音格式必须是mp3才能播放

  • window.URL.createObjectURL传入blob数据在pad端报错,转不了

以下为解决这两个问题的方案。

步骤3

以下为我实现 录音格式为mp3 和 window.URL.createObjectURL传入blob数据在pad端报错 的方案。

1、修改HZRecorder里的audioData对象代码。并引入网上一位大神的一个js文件lamejs.js

const lame = new lamejs();let audioData = {    samplesMono: null,    maxSamples: 1152,    mp3Encoder: new lame.Mp3Encoder(1, context.sampleRate || 44100, config.bitRate || 128),    dataBuffer: [],    size: 0, // 录音文件长度    buffer: [], // 录音缓存    inputSampleRate: context.sampleRate, // 输入采样率    inputSampleBits: 16, // 输入采样数位 8, 16    outputSampleRate: config.sampleRate, // 输出采样率    oututSampleBits: config.sampleBits, // 输出采样数位 8, 16    convertBuffer: function(arrayBuffer) {        let data = new Float32Array(arrayBuffer);        let out = new Int16Array(arrayBuffer.length);        this.floatTo16BitPCM(data, out);        return out;    },    floatTo16BitPCM: function(input, output) {        for (let i = 0; i < input.length; i++) {            let s = Math.max(-1, Math.min(1, input[i]));            output[i] = s < 0 ? s * 0x8000 : s * 0x7fff;        }    },    appendToBuffer: function(mp3Buf) {        this.dataBuffer.push(new Int8Array(mp3Buf));    },    encode: function(arrayBuffer) {        this.samplesMono = this.convertBuffer(arrayBuffer);        let remaining = this.samplesMono.length;        for (let i = 0; remaining >= 0; i += this.maxSamples) {            let left = this.samplesMono.subarray(i, i + this.maxSamples);            let mp3buf = this.mp3Encoder.encodeBuffer(left);            this.appendToBuffer(mp3buf);            remaining -= this.maxSamples;        }    },    finish: function() {        this.appendToBuffer(this.mp3Encoder.flush());        return new Blob(this.dataBuffer, { type: 'audio/mp3' });    },    input: function(data) {        this.buffer.push(new Float32Array(data));        this.size += data.length;    },    compress: function() {        // 合并压缩        // 合并        let data = new Float32Array(this.size);        let offset = 0;        for (let i = 0; i < this.buffer.length; i++) {            data.set(this.buffer[i], offset);            offset += this.buffer[i].length;        }        // 压缩        let compression = parseInt(this.inputSampleRate / this.outputSampleRate, 10);        let length = data.length / compression;        let result = new Float32Array(length);        let index = 0;        let j = 0;        while (index < length) {            result[index] = data[j];            j += compression;            index++;        }        return result;    },    encodeWAV: function() {        let sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate);        let sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits);        let bytes = this.compress();        let dataLength = bytes.length * (sampleBits / 8);        let buffer = new ArrayBuffer(44 + dataLength);        let data = new DataView(buffer);        let channelCount = 1; // 单声道        let offset = 0;        let writeString = function(str) {            for (let 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 (let i = 0; i < bytes.length; i++, offset++) {                const s = Math.max(-1, Math.min(1, bytes[i]));                let val = s < 0 ? s * 0x8000 : s * 0x7fff;                val = parseInt(255 / (65535 / (val + 32768)), 10);                data.setInt8(offset, val, true);            }        } else {            for (let i = 0; i < bytes.length; i++, offset += 2) {                const 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' });    }};

2、修改HZRecord的音频采集的调用方法。

// 音频采集recorder.onaudioprocess = function(e) {    audioData.encode(e.inputBuffer.getChannelData(0));};

3、HZRecord的getBlob方法。

this.getBlob = function() {    this.stop();    return audioData.finish();};

4、HZRecord的play方法。把blob转base64url。

this.play = function(func) {    readBlobAsDataURL(this.getBlob(), func);};function readBlobAsDataURL(data, callback) {    let fileReader = new FileReader();    fileReader.onload = function(e) {        callback(e.target.result);    };    fileReader.readAsDataURL(data);}

至此,已经解决以上两个问题。

步骤4

这里主要介绍怎么做录音时的动效。

根据传入的音量大小,做一个圆弧动态扩展。

// 创建analyser节点,获取音频时间和频率数据const analyser = context.createAnalyser();audioInput.connect(analyser);const inputAnalyser = new Uint8Array(1);const wrapEle = $this.refs['wrap'];let ctx = wrapEle.getContext('2d');const width = wrapEle.width;const height = wrapEle.height;const center = {    x: width / 2,    y: height / 2};function drawArc(ctx, color, x, y, radius, beginAngle, endAngle) {    ctx.beginPath();    ctx.lineWidth = 1;    ctx.strokeStyle = color;    ctx.arc(x, y, radius, (Math.PI * beginAngle) / 180, (Math.PI * endAngle) / 180);    ctx.stroke();}(function drawSpectrum() {    analyser.getByteFrequencyData(inputAnalyser); // 获取频域数据    ctx.clearRect(0, 0, width, height);    // 画线条    for (let i = 0; i < 1; i++) {        let value = inputAnalyser[i] / 3; // <===获取数据        let colors = [];        if (value <= 16) {            colors = ['#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4', '#e4e4e4', '#e4e4e4'];        } else if (value <= 32) {            colors = ['#f5A631', '#f5A631', '#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4'];        } else {            colors = ['#f5A631', '#f5A631', '#f5A631', '#f5A631', '#f5A631', '#f5A631'];        }        drawArc(ctx, colors[0], center.x, center.y, 52 + 16, -30, 30);        drawArc(ctx, colors[1], center.x, center.y, 52 + 16, 150, 210);        drawArc(ctx, colors[2], center.x, center.y, 52 + 32, -22.5, 22.5);        drawArc(ctx, colors[3], center.x, center.y, 52 + 32, 157.5, 202.5);        drawArc(ctx, colors[4], center.x, center.y, 52 + 48, -13, 13);        drawArc(ctx, colors[5], center.x, center.y, 52 + 48, 167, 193);    }    // 请求下一帧    requestAnimationFrame(drawSpectrum);})();

到此,关于"html5录音功能如何实现"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

数据 文件 方法 功能 字节 样本 声道 位数 对象 格式 音频 输入 大小 数位 方案 缓存 输出 标志 标识 标识符 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 松江区网络技术转让包括什么 旧台式电脑改服务器 云服务器数据会被监控吗 农场小游戏用什么数据库呢 黑暗之魂3怎么服务器登录不上去 vb通过ado连接数据库 育碧服务器怎么登陆不上网 台州运营网络技术优势 魔兽怀旧服切换服务器 60年代 魔兽 最好的服务器 软件开发计划百度文库 想学软件开发菏泽哪家学校好 一般用什么工具连接数据库 数据库表没设置主键可以吗 奥丁神叛韩服有几个服务器 互联网视觉科技有限公司 调出代理服务器设置界面 初心服务器怎么关闭新手保护 互联网是科技的发展 通信网络安全分为数据传输完整性 改了 数据库端口 湖北工程软件开发商 幻塔安卓服务器没有星岛 甘肃腾悦网络技术有限公司 有名的web服务器 access的关系数据库 正定兜安网络技术有限公司 银行账户管理服务器 数据库字段time是什么类型 数据库主键pk
0