프로젝트 요구로 인해 웹 측에서 녹음 기능을 구현해야 합니다. 처음에는 두 가지 솔루션이 발견되었습니다. 하나는 iframe을 통한 것이고 다른 하나는 html5의 getUserMedia API였습니다. 우리의 녹음 기능은 IE 브라우저와 호환될 필요가 없기 때문에 이를 구현하기 위해 주저하지 않고 html5에서 제공하는 getUserMedia를 선택했습니다. 기본 아이디어는 이를 공식 API 문서 및 온라인에서 찾을 수 있는 일부 솔루션과 결합하여 프로젝트 요구 사항에 맞는 솔루션을 만드는 것입니다. 하지만 녹음 기능이 패드와 PC 모두에서 동시에 켜질 수 있도록 보장해야 하기 때문에 몇 가지 함정도 있습니다. 다음은 프로세스 복원입니다.
1단계새 API가 navigator.mediaDevices.getUserMedia를 전달하고 약속을 반환하기 때문입니다.
기존 API가 navigator.getUserMedia이므로 호환성이 생겼습니다. 코드는 다음과 같습니다:
// 이전 브라우저는 mediaDevices를 전혀 구현하지 않을 수 있으므로 먼저 빈 객체를 설정할 수 있습니다. if (navigator.mediaDevices === undefine) { navigator.mediaDevices = {};}// 일부 브라우저는 mediaDevices를 부분적으로 지원합니다. 기존 속성을 덮어쓸 수 있으므로 객체에 getUserMedia//를 직접 설정할 수 없습니다. 여기서는 getUserMedia 속성이 존재하지 않는 경우에만 추가합니다. if (navigator.mediaDevices.getUserMedia === 정의되지 않음) { let getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia = function(constraints) { // 먼저, getUserMedia가 있으면 가져옵니다. // 일부 브라우저는 이를 전혀 구현하지 않습니다. 그런 다음 통합 인터페이스를 유지하기 위해 약속의 거부에 오류를 반환합니다. if (!getUserMedia) { return Promise.reject(new Error(' getUserMedia is 이 브라우저에서는 구현되지 않았습니다.')); } // 그렇지 않으면 이전 navigator.getUserMedia 메소드에 대한 Promise를 래핑합니다. return new Promise(function(resolve, 거부) { getUserMedia.call(navigator, 제약 조건, 해결, 거부) });2단계
이는 HZRecorder를 캡슐화하여 인터넷에 존재하는 방법입니다. 기본적으로 이 방법을 참조합니다. 녹음 인터페이스는 HZRecorder.get을 호출하여 호출할 수 있습니다. 이 메소드는 새로운 HZRecorder 이후에 콜백 함수가 실행되고 구체화된 HZRecorder 객체가 전달됩니다. 이 객체의 메소드를 통해 녹화 시작, 일시정지, 정지, 재생 등의 기능을 구현할 수 있습니다.
var HZRecorder = function (stream, config) { config = config || {}; config.sampleBits = config.sampleBits || 8; //샘플링 비트 8, 16 config.sampleRate || ; //샘플링 레이트(1/6 44100) //오디오 환경 객체 생성 audioContext = window.AudioContext || window.webkitAudioContext; var context = new audioContext(); //이 객체에 사운드 입력 var audioInput = context.createMediaStreamSource(stream); //볼륨 노드 설정 var Volume = audioInput .connect(volume); //사운드를 캐시하기 위한 캐시 생성 var bufferSize = 4096; //사운드 캐시 노드 생성, createScriptProcessor 메소드 // 두 번째와 세 번째 매개변수는 입력과 출력이 모두 바이노럴임을 나타냅니다. var Recorder = context.createScriptProcessor(bufferSize, 2, 2); var audioData = { size: 0 //녹음 파일 길이, 버퍼: [] //녹음 캐시, inputSampleRate: context.sampleRate //입력 샘플링 속도, inputSampleBits: 16 //샘플링 숫자 8, 16 입력, outputSampleRate: config.sampleRate //샘플링 레이트 출력, oututSampleBits: config.sampleBits //출력 샘플 비트 8, 16, 입력: function (data) { this.buffer.push(new Float32Array(data)) this.size += data.length }, 압축: 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 압축 =parseInt(this.inputSampleRate / this.outputSampleRate); / 압축; var result = new Float32Array(length); var index = 0, j = 0; while (index < length) { result[index] = data[j]; index++; } 결과 반환; } , encodeWAV: function () { var SampleRate = Math.min(this.inputSampleRate, this.outputSampleRate); var SampleBits = Math.min(this.inputSampleBits, this.oututSampleBits); .compress(); var dataLength = bytes.length * (sampleBits / 8); ArrayBuffer(44 + dataLength); var data = new DataView(buffer); var ChannelCount = 1; //Mono 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'); // 파형 형식 플래그 writeString('fmt '); // 필터 바이트, 일반적으로 0x10 = 16 data.setUint32(offset, 16, true); offset += 4; // 형식 범주(PCM 형식 샘플링 데이터) data.setUint16(offset, 1, true); // 채널 수 data.setUint16; ( offset, ChannelCount, true); offset += 2; // 초당 샘플 수인 샘플링 속도는 각 채널의 재생 속도를 나타냅니다. true); 오프셋 += 4; //파형 데이터 전송 속도(초당 평균 바이트) 모노 × 초당 데이터 비트 × 샘플당 데이터 비트/8 data.setUint32(offset,channelCount *sampleRate * (sampleBits / 8), true ); offset += 4; // 한 번에 빠른 데이터 조정 수 샘플링이 차지하는 바이트 수는 mono × 샘플당 데이터 비트 수/8입니다. data.setUint16(offset, ChannelCount * (sampleBits / 8), true); offset += 2; // 샘플당 데이터 비트 수 data.setUint16(offset, SampleBits, true); // 데이터 식별자 writeString('data'); += 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]))); s < 0 ? s * 0x8000 : s * 0x7FFF; val = parsInt(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); connect(context.destination); }; //중지 this.stop = function () { Recorder.disconnect() }; //End 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 (콜백) { xhr.upload.addEventListener('progress', function (e ) { callback('업로드', e) }, false) xhr.addEventListener('load', function (e) { callback('ok', e); }, false); xhr.addEventListener('error', function (e) { callback('error', e); }, false); (e) { 콜백('취소', e) }, false) } xhr.open('POST', url); //오디오 수집 레코더.onaudioprocess = function (e) { audioData.input(e.inputBuffer.getChannelData(0)); //record(e.inputBuffer.getChannelData(0)) }; .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); 콜백(rec); }) .catch(function(error) { HZRecorder.throwError('녹화할 수 없습니다. 장치 상태를 확인하세요.'); }) }};
위의 내용은 이미 대부분의 요구 사항을 충족할 수 있습니다. 하지만 패드측과 호환이 되어야 합니다. 우리 패드에는 해결해야 할 몇 가지 문제가 있습니다.
다음은 두 가지 문제에 대한 해결책입니다.
3단계다음은 녹음 형식을 mp3 및 window.URL.createObjectURL로 구현하여 Blob 데이터를 전달하고 패드 측에서 오류를 보고하는 솔루션입니다.
1. HZRecorder에서 audioData 개체 코드를 수정합니다. 그리고 인터넷에 떠도는 대단한 분의 js 파일 lamejs.js를 소개해주세요
const lame = new lamejs();let audioData = { SampleMono: null, maxSamples: 1152, mp3Encoder: new lame.Mp3Encoder(1, context.sampleRate || 44100, config.bitRate || 128), dataBuffer: [], size : 0, // 녹음 파일 길이 버퍼: [], // 녹음 버퍼 inputSampleRate: context.sampleRate, // 입력 샘플링 레이트 inputSampleBits: 16, // 입력 샘플 숫자 8, 16 outputSampleRate: config.sampleRate, // 출력 샘플 레이트 outputSampleBits: config.sampleBits, // 출력 샘플 숫자 8, 16 ConvertBuffer: function(arrayBuffer) { 데이터 = 새 데이터 제공 Float32Array(arrayBuffer); let out = new Int16Array(arrayBuffer.length); this.floatTo16BitPCM(data, out); return out; }, floatTo16BitPCM: function(input, output) { for (let i = 0; i < input. 길이; i++) { let s = Math.max(-1, Math.min(1, input[i])); = s < 0 ? s * 0x8000 : s * 0x7fff; } },appendToBuffer: function(mp3Buf) { this.dataBuffer.push(new Int8Array(mp3Buf) }, encode: function(arrayBuffer) { this.samplesMono = this .convertBuffer(arrayBuffer); 남은 값 = this.samplesMono.length; (i = 0; 나머지 >= 0; i += this.maxSamples) { let left = this.samplesMono.subarray(i, i + this.maxSamples); let mp3buf = this.mp3Encoder.encodeBuffer(left); .appendToBuffer(mp3buf); 남음 -= this.maxSamples } }, 완료: function() { this.appendToBuffer(this.mp3Encoder.flush()); return new Blob(this.dataBuffer, { type: 'audio/mp3' }) }, 입력: function(data) { this.buffer.push(new Float32Array( data)); this.size += data.length }, 압축: 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) += this.buffer [i].length; } // 압축 let 압축 =parseInt(this.inputSampleRate / this.outputSampleRate, 10); let result = new; Float32Array(length); let j = 0; while (index < length) { result[index] = data[j] += index++ }, encodeWAV: 함수 { 샘플Rate = Math.min(this.inputSampleRate, this.outputSampleRate); 샘플Bits = Math.min(this.inputSampleBits, this.oututSampleBits); let bytes = this.compress(); let dataLength = bytes.length * (sampleBits / 8); let buffer = new ArrayBuffer(44 + dataLength); let dataView(buffer); 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); // WAV 파일 플래그 writeString('WAVE'); // 웨이브 형식 플래그 writeString('fmt '); // 필터 바이트, 일반적으로 0x10 = 16 data.setUint32(offset, 16, true) // 형식 범주(PCM 형식 샘플링) data) data.setUint16(offset, 1, true); offset += 2; // 채널 번호 data.setUint16(offset, ChannelCount, true); += 2; // 샘플링 속도, 각 채널의 재생 속도를 나타내는 초당 샘플 수 data.setUint32(offset, SampleRate, true); // 파형 데이터 전송 속도(평균 바이트 수) 초당 ) 모노 × 초당 데이터 비트 × 샘플당 데이터 비트 / 8 data.setUint32(offset,channelCount *sampleBits / 8), true); 하나의 샘플 모노 채널이 차지하는 빠른 데이터 조정 바이트 수 data.setUint16(offset, SampleBits, true); // 데이터 식별자 writeString('data'); // 총 개수 샘플링된 데이터, 즉 전체 데이터 크기 - 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 = parsInt(255 / (65535 / (val + 32768)), 10); data.setInt8(offset, val, true) } } else { for (let i = 0; i < bytes.length; i++ , 오프셋 += 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의 재생 방법. Blob을 base64url로 변환합니다.
this.play = function(func) { readBlobAsDataURL(this.getBlob(), func);};function readBlobAsDataURL(data, callback) { let fileReader = new FileReader.onload = function(e) { callback(e .target.result) }; fileReader.readAsDataURL(data);}
현재까지 위의 두 가지 문제는 해결되었습니다.
4단계여기에서는 녹음 중에 역동적인 효과를 만드는 방법을 주로 소개합니다. 애니메이션 요구 사항 중 하나는 다음과 같습니다.
들어오는 양에 따라 원호가 동적으로 확장됩니다.
//분석기 노드를 생성하고 오디오 시간 및 주파수 데이터 가져오기 const analyzer = context.createAnalyser();audioInput.connect(analyser);const inputAnalyser = new Uint8Array(1);const WrapEle = $this.refs['wrap'] ; ctx = WrapEle.getContext('2d');const 너비 = WrapEle.width;const 높이 = WrapEle.height;const center = { x: 너비 / 2, y: 높이 / 2}; function drawArc(ctx, color, x, y, radius, BeginAngle, endAngle) { ctx.beginPath(); ; ctx.StrokeStyle = 색상; ctx.arc(x, y, 반경, (Math.PI * startAngle) / 180, (Math.PI * endAngle) / 180); ctx.Stroke();}(function drawSpectrum() { analyzer.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 (값 <= 32) { 색상 = ['#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, 색상[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는 이 github를 참조할 수 있습니다.
위 내용은 이 기사의 전체 내용입니다. 모든 분들의 학습에 도움이 되기를 바랍니다. 또한 모든 분들이 VeVb Wulin Network를 지지해 주시길 바랍니다.