HTML5音频录制

学习HTML5音频录制

之前玩过WebRTC,通过HTML5可以轻松实现语音视频聊天,网上的示例很多。最近想了解下HTML5能否录制音频,用什么接口可以做到。这里分享下学习资料。

浏览器

要获取音频和视频,需要用到getUserMedia。桌面平台支持的浏览器包括Chrome, Firefox, Opera和Edge。移动平台包括Chrome, Firefox和Opera,不过只是Android平台,iOS不行。微软似乎也放弃IE了,全力支持Edge。苹果对HTML5的支持始终不够。前段日子还有新闻报道说有开发者针对HTML5起诉苹果。

获取视频音频流

网上示例很多,从Mozilla的文档中找了一段最简单的代码:


      
  1. var p = navigator.mediaDevices.getUserMedia({ audio: true, video: true });
  2. p.then(function(mediaStream) {
  3. var video = document.querySelector('video');
  4. video.src = window.URL.createObjectURL(mediaStream);
  5. video.onloadedmetadata = function(e) {
  6. // Do something with the video here.
  7. video.play();
  8. };
  9. });
  10. p.catch(function(err) { console.log(err.name); }); // always check for errors at the end.

录制Audio

在Mozilla的HTML5文档中看到了MediaRecorder。这个接口简单方便,但是比较新,浏览器的兼容性有限。

桌面

移动

不过也有替代方案,使用AudioNodes。基本步骤如下:

1. 通过getUserMedia获取视频音频流。

2. 通过createMediaStreamSource创建MediaStreamAudioSourceNode


      
  1. if (navigator.getUserMedia) {
  2. console.log('getUserMedia supported.');
  3. navigator.getUserMedia (
  4. // constraints: audio and video for this app
  5. {
  6. audio: true,
  7. video: true
  8. },
  9. // Success callback
  10. function(stream) {
  11. video.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
  12. video.onloadedmetadata = function(e) {
  13. video.play();
  14. video.muted = 'true';
  15. };
  16. // Create a MediaStreamAudioSourceNode
  17. // Feed the HTMLMediaElement into it
  18. var source = audioCtx.createMediaStreamSource(stream);
  19. },
  20. // Error callback
  21. function(err) {
  22. console.log('The following gUM error occured: ' + err);
  23. }
  24. );
  25. } else {
  26. console.log('getUserMedia not supported on your browser!');
  27. }

3. 链接AudioNodes。创建ScriptProcessorNode。通过onaudioprocess来获取音频数据。


      
  1. var scriptNode = audioCtx.createScriptProcessor( 4096, 1, 1);
  2. scriptNode.onaudioprocess = function(audioProcessingEvent) {
  3. // The input buffer is the song we loaded earlier
  4. var inputBuffer = audioProcessingEvent.inputBuffer;
  5. // Loop through the output channels (in this case there is only one)
  6. for ( var channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
  7. var inputData = inputBuffer.getChannelData(channel);
  8. }
  9. }
  10. source.connect(scriptNode);
  11. scriptNode.connect(audioCtx.destination);

4. 通过XHR或者WebSockets来发送blob数据。

JavaScript库

流程就这么简单,但是写起来还是比较复杂的。所以需要参考下别人写的代码。在GitHub上可以找到RecordRTCRecorderjs。前者可以录制视频和音频,如果只想录制音频,可以选择后面这个,比较简单。

关于RecordRTC,有一个站点可以体验视频音频录制:online demo

现在分析下Recorderjs的源码。

在onaudioprocess中获取音频buffer:


      
  1. this.context = source.context;
  2. this.node = ( this.context.createScriptProcessor || this.context.createJavaScriptNode).call( this.context, this.config.bufferLen, this.config.numChannels, this.config.numChannels);
  3. this.node.onaudioprocess = function (e) {
  4. if (!_this.recording) return;
  5. var buffer = [];
  6. for ( var channel = 0; channel < _this.config.numChannels; channel++) {
  7. buffer.push(e.inputBuffer.getChannelData(channel));
  8. }
  9. _this.worker.postMessage({
  10. command: 'record',
  11. buffer: buffer
  12. });
  13. };
  14. source.connect( this.node);
  15. this.node.connect( this.context.destination); //this should not be necessary

用数组存储音频buffer:


      
  1. function record(inputBuffer) {
  2. for ( var channel = 0; channel < numChannels; channel++) {
  3. recBuffers[channel].push(inputBuffer[channel]);
  4. }
  5. recLength += inputBuffer[ 0].length;
  6. }

WAV格式编码:


      
  1. function encodeWAV(samples) {
  2. var buffer = new ArrayBuffer(44 + samples.length * 2);
  3. var view = new DataView(buffer);
  4. /* RIFF identifier */
  5. writeString(view, 0, 'RIFF');
  6. /* RIFF chunk length */
  7. view.setUint32(4, 36 + samples.length * 2, true);
  8. /* RIFF type */
  9. writeString(view, 8, 'WAVE');
  10. /* format chunk identifier */
  11. writeString(view, 12, 'fmt ');
  12. /* format chunk length */
  13. view.setUint32(16, 16, true);
  14. /* sample format (raw) */
  15. view.setUint16(20, 1, true);
  16. /* channel count */
  17. view.setUint16(22, numChannels, true);
  18. /* sample rate */
  19. view.setUint32(24, sampleRate, true);
  20. /* byte rate (sample rate * block align) */
  21. view.setUint32(28, sampleRate * 4, true);
  22. /* block align (channel count * bytes per sample) */
  23. view.setUint16(32, numChannels * 2, true);
  24. /* bits per sample */
  25. view.setUint16(34, 16, true);
  26. /* data chunk identifier */
  27. writeString(view, 36, 'data');
  28. /* data chunk length */
  29. view.setUint32(40, samples.length * 2, true);
  30. floatTo16BitPCM(view, 44, samples);
  31. return view;
  32. }

合并所有的buffer,并用WAV格式导出,最后转换成blob:


      
  1. function exportWAV(type) {
  2. var buffers = [];
  3. for ( var channel = 0; channel < numChannels; channel++) {
  4. buffers.push(mergeBuffers(recBuffers[channel], recLength));
  5. }
  6. var interleaved = undefined;
  7. if (numChannels === 2) {
  8. interleaved = interleave(buffers[ 0], buffers[ 1]);
  9. } else {
  10. interleaved = buffers[ 0];
  11. }
  12. var dataview = encodeWAV(interleaved);
  13. var audioBlob = new Blob([dataview], { type: type });
  14. }

这样就可以保存或者发送录制的音频文件了。

参考资料

Html5网页纯JavaScript录制MP3音频 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Html5网页JavaScript录制MP3音频</title> <meta charset="utf-8" /> </head> <body> Html5网页JavaScript录制MP3音频 录制 停止 上传 调试信息: [removed][removed] [removed] var recorder = new MP3Recorder({ debug:true, funOk: function () { btnStart.disabled = false; log('初始化成功'); }, funCancel: function (msg) { log(msg); recorder = null; } }); var mp3Blob; function funStart(button) { btnStart.disabled = true; btnStop.disabled = false; btnUpload.disabled = true; log('录音开始...'); recorder.start(); } function funStop(button) { recorder.stop(); btnStart.disabled = false; btnStop.disabled = true; btnUpload.disabled = false; log('录音结束,MP3导出中...'); recorder.getMp3Blob(function (blob) { log('MP3导出成功'); mp3Blob = blob; var url = URL.createObjectURL(mp3Blob); var div = document.createElement('div'); var au = document.createElement('audio'); var hf = document.createElement('a'); au.controls = true; au.src = url; hf.href = url; hf.download = new Date().toISOString() + '.mp3'; hf[removed] = hf.download; div.appendChild(au); div.appendChild(hf); recordingslist.appendChild(div); }); } function log(str) { recordingslist[removed] += str + ''; } function funUpload() { var fd = new FormData(); var mp3Name = encodeURIComponent('audio_recording_' + new Date().getTime() + '.mp3'); fd.append('mp3Name', mp3Name); fd.append('file', mp3Blob); var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { recordingslist[removed] += '上传成功:' + mp3Name + ''; } }; xhr.open('POST', 'upload.ashx'); xhr.send(fd); } [removed] </body> </html> [javascript] view plain copy 在CODE上查看代码片派生到我的代码片 (function (exports) { var MP3Recorder = function (config) { var recorder = this; config = config || {}; config.sampleRate = config.sampleRate || 44100; config.bitRate = config.bitRate || 128; navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; if (navigator.getUserMedia) { navigator.getUserMedia({ audio: true }, function (stream) { var context = new AudioContext(), microphone = context.createMediaStreamSource(stream), processor = context.createScriptProcessor(16384, 1, 1),//bufferSize大小,输入channel数,输出channel数 mp3ReceiveSuccess, currentErrorCallback; config.sampleRate = context.sampleRate; processor.onaudioprocess = function (event) { //边录音边转换 var array = event.inputBuffer.getChannelData(0); realTimeWorker.postMessage({ cmd: 'encode', buf: array }); }; var realTimeWorker = new Worker('js/worker-realtime.js'); realTimeWorker.onmessage = function (e) { switch (e.data.cmd) { case 'init': log('初始化成功'); if (config.funOk) { config.funOk(); } break; case 'end': log('MP3大小:', e.data.buf.length); if (mp3ReceiveSuccess) { mp3ReceiveSuccess(new Blob(e.data.buf, { type: 'audio/mp3' })); } break; case 'error': log('错误信息:' + e.data.error); if (currentErrorCallback) { currentErrorCallback(e.data.error); } break; default: log('未知信息:', e.data); } }; recorder.getMp3Blob = function (onSuccess, onError) { currentErrorCallback = onError; mp3ReceiveSuccess = onSuccess; realTimeWorker.postMessage({ cmd: 'finish' }); }; recorder.start = function () { if (processor && microphone) { microphone.connect(processor); processor.connect(context.destination); log('开始录音'); } } recorder.stop = function () { if (processor && microphone) { microphone.disconnect(); processor.disconnect(); log('录音结束'); } } realTimeWorker.postMessage({ cmd: 'init', config: { sampleRate: config.sampleRate, bitRate: config.bitRate } }); }, function (error) { var msg; switch (error.code || error.name) { case 'PERMISSION_DENIED': case 'PermissionDeniedError': msg = '用户拒绝访问麦客风'; break; case 'NOT_SUPPORTED_ERROR': case 'NotSupportedError': msg = '浏览器不支持麦客风'; break; case 'MANDATORY_UNSATISFIED_ERROR': case 'MandatoryUnsatisfiedError': msg = '找不到麦客风设备'; break; default: msg = '无法打开麦克风,异常信息:' + (error.code || error.name); break; } if (config.funCancel) { config.funCancel(msg); } }); } else { if (config.funCancel) { config.funCancel('当前浏览器不支持录音功能'); } } function log(str) { if (config.debug) { console.log(str); } } } exports.MP3Recorder = MP3Recorder; })(window);
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值