首页 > 作文

html5录音功能实战示例

更新时间:2023-04-03 03:35:15 阅读: 评论:0

缘起

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

步骤1

由于新的api是通过navigator.mediadevices.geturmedia,且返回一个promi。

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

// 老的浏览器可能根本没有实现 mediadevices,所以我们可以先设置一个空的对象if (navigator.mediadevices === undefined) {    navigator.mediadevices = {};}// 一些浏览器部分支持 mediadevices。我们不能直接给对象设置 geturmedia// 因为这样可能会覆盖已有的属性。这里我们只会在没有geturmedia属性的时候添加它。if (navigator.mediadevices.geturmedia === undefined) {    let geturmedia =        navigator.geturmedia ||        navigator.webkitgeturmedia ||        navigator.mozgeturmedia ||        navigator.msgeturmedia;    navigator.mediadevices.geturmedia = function(constraints) {        // 首先,如果有geturmedia的话,就获得它        // 一些浏览器根本没实现它 - 那么就返回一个error到promi的reject来保持一个统一的接口        if (!geturmedia) {            return promi.reject(new error('geturmedia is not implemented in this browr'));        }        // 否则,为老的navigator.geturmedia方法包裹一个promi        return new promi(function(resolve, reject) {            geturmedia.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)            //创建一个音频环境对象      audioc谢宇威ontext = 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 offt = 0;              for (var i = 0; i < this.buffer.length; i++) {                  data.t(this.buffer[i], offt);                  offt += this.buffer[i].length;              }              //压缩              var compression = parint(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 offt = 0;              var writestring = function (str) {                  for (var i = 0; i < str.length; i++) {                      data.tuint8(offt + i, str.charcodeat(i));                  }              };                            // 资源交换文件标识符               writestring('riff'); offt += 4;              // 下个地址开始到文件尾总字节数,即文件大小-8               data.tuint32(offt, 36 + datalength, true); offt += 4;              // wav文件标志              writestring('wave'); offt += 4;              // 波形格式标志               writestring('fmt '); offt += 4;              // 过滤字节,一般为 0x10 = 16               data.tuint32(offt, 16, true); offt += 4;              // 格式类别 (pcm形式采样数据)               data.tuint16(offt, 1, true); offt += 2;              // 通道数               data.tuint16(offt, channelcount, true); offt += 2;              // 采样率,每秒样本数,表示每个通道的播放速度               data.tuint32(offt, samplerate, true); offt += 4;              // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8               data.tuint32(offt, channelcount * samplerate * (samplebits / 8), true); offt += 4;              // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8               data.tuint16(offt, channelcount * (samplebits / 8), true); offt += 2;              // 每样本数据位数               data.tuint16(offt, samplebits, true); offt += 2;              // 数据标识符               writestring('data'); offt += 4;              // 采样数据总数,即数据总大小-44               data.tuint32(offt, datalength, true); offt += 4;              // 写入采样数据               if (samplebits === 8) {                  for (var i = 0; i < bytes.length; i++, offt++) {                      var s = math.max(-1, math.min(1, bytes[i]));                      var val = s < 0 ? s * 0x8000 : s * 0x7fff;                      val = parint(255 / (65535 / (val + 32768)));                      data.tint8(offt, val, true);                  }              } el {                  for (var i = 0; i < bytes.length; i++, offt += 2) {                      var s = math.max(-1, math.min(1, bytes[i]));                      data.tint16(offt, 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.clo();    };        // 继续    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);              }, fal);              xhr.addeventlistener('load', function (e) {                  callback('ok', e);              }, fal);              xhr.addeventlistener('error', function (e) {                  callback('error', e);              }, fal);              xhr.addeventlistener('abort', function (e) {                  callback('cancel', e);              }, fal);          }          xhr.open('post', url);          xhr.nd(fd);      };      //音频采集      recorder.onaudioprocess = function (e) {          audiodata.input(e.inputbuffer.getchanneldata(0));          //record(e.inputbuffer.getchanneldata(0));      };  };  //抛出异常  hzrecorder.throwerror = function (message) {      throw new functio200wn () { this.tostring = function () { return message; };};  };  //是否支持录音  hzrecorder.canrecording = (navigator.geturmedia != null);  //获取录音机  hzrecorder.get = function (callback2020年最后一天祝福语, config) {     if (callback) {        navigator.mediadevices            .geturmedia({ 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;    },    floa元宵节灯笼tto16bitpcm: 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 offt = 0;        for (let i = 0; i < this.buffer.length; i++) {            data.t(this.buffer[i], offt);            offt += this.buffer[i].length;        }        // 压缩        let compression = parint(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 offt = 0;        let writestring = function(str) {            for (let i = 0; i < str.length; i++) {                data.tuint8(offt + i, str.charcodeat(i));            }        };        // 资源交换文件标识符        writestring('riff');        offt += 4;        // 下个地址开始到文件尾总字节数,即文件大小-8        data.tuint32(offt, 36 + datalength, true);        offt += 4;        // wav文件标志        writestring('wave');        offt += 4;        // 波形格式标志        writestring('fmt ');        offt += 4;        // 过滤字节,一般为 0x10 = 16        data.tuint32(offt, 16, true);        offt += 4;        // 格式类别 (pcm形式采样数据)        data.tuint16(offt, 1, true);        offt += 2;        // 通道数        data.tuint16(offt, channelcount, true);        offt += 2;        // 采样率,每秒样本数,表示每个通道的播放速度        data.tuint32(offt, samplerate, true);        offt += 4;        // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8        data.tuint32(offt, channelcount * samplerate * (samplebits / 8洗澡的拼音), true);        offt += 4;        // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8        data.tuint16(offt, channelcount * (samplebits / 8), true);        offt += 2;        // 每样本数据位数        data.tuint16(offt, samplebits, true);        offt += 2;        // 数据标识符        writestring('data');        offt += 4;        // 采样数据总数,即数据总大小-44        data.tuint32(offt, datalength, true);        offt += 4;        // 写入采样数据        if (samplebits === 8) {            for (let i = 0; i < bytes.length; i++, offt++) {                const s = math.max(-1, math.min(1, bytes[i]));                let val = s < 0 ? s * 0x8000 : s * 0x7fff;                val = parint(255 / (65535 / (val + 32768)), 10);                data.tint8(offt, val, true);            }        } el {            for (let i = 0; i < bytes.length; i++, offt += 2) {                const s = math.max(-1, math.min(1, bytes[i]));                data.tint16(offt, 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转ba64url。

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

这里主要介绍怎么做录音时的动效。我们的一个动效需求为:

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

// 创建analyr节点,获取音频时间和频率数据const analyr = context.createanalyr();audioinput.connect(analyr);const inputanalyr = 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() {    analyr.getbytefrequencydata(inputanalyr); // 获取频域数据    ctx.clearrect(0, 0, width, height);    // 画线条    for (let i = 0; i < 1; i++) {        let value = inputanalyr[i] / 3; // <===获取数据        let colors = [];        if (value <= 16) {            colors = ['#f5a631', '#f5a631', '#e4e4e4', '#e4e4e4', '#e4e4e4', '#e4e4e4'];        } el if (value <= 32) {            colors = ['#f5a631', '#f5a631', '#f5a631', '#f5a631', '#e4e4e4', '#e4e4e4'];        } el {            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录音功能方案已经完成。有什么需要补充,不合理的地方的欢迎留言。

ps:lamejs可参考这个

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持www.887551.com。

本文发布于:2023-04-03 03:35:13,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/7e583542454c8404d4a992c2b7f4c847.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:html5录音功能实战示例.doc

本文 PDF 下载地址:html5录音功能实战示例.pdf

标签:数据   样本   字节   文件
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图