Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8835

【WebAudioAPI】録音した音声をバイナリデータ化、PHPへ受け渡し

$
0
0

概要

Node.js上で、IBMのWatsonによって人が話した音声データを自動で文字起こしするスクリプトを作成しました。
その中で、結構苦労した
PCのマイクに直接アクセス→録音した音声データをバイナリデータ化、PHPへ受け渡し
の部分をメモがてら貼り付け。

環境

$php -v
PHP 7.1.23 (cli) (built: Feb 22 2019 22:19:32) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies

録音部分

hogehoge.js
// 音声データのバッファをクリアするaudioData=[];//様々なブラウザでマイクへのアクセス権を取得するnavigator.mediaDevices=navigator.mediaDevices||navigator.webkitGetUserMedia;//audioのみtrue。Web Audioが問題なく使えるのであれば、第二引数で指定した関数を実行navigator.getUserMedia({audio:true,video:false},successFunc,errorFunc);functionsuccessFunc(stream){constaudioContext=newAudioContext();sampleRate=audioContext.sampleRate;// ストリームを合成するNodeを作成constmediaStreamDestination=audioContext.createMediaStreamDestination();// マイクのstreamをMediaStreamNodeに入力constaudioSource=audioContext.createMediaStreamSource(stream);audioSource.connect(mediaStreamDestination);// 接続先のstreamをMediaStreamに入力for(letstreamofremoteAudioStream){try{audioContext.createMediaStreamSource(stream).connect(mediaStreamDestination);}catch(e){console.log(e);}}// マイクと接続先を合成したMediaStreamを取得constcomposedMediaStream=mediaStreamDestination.stream;// マイクと接続先を合成したMediaStreamSourceNodeを取得constcomposedAudioSource=audioContext.createMediaStreamSource(composedMediaStream);// 音声のサンプリングをするNodeを作成constaudioProcessor=audioContext.createScriptProcessor(1024,1,1);// マイクと接続先を合成した音声をサンプリングcomposedAudioSource.connect(audioProcessor);audioProcessor.addEventListener('audioprocess',event=>{audioData.push(event.inputBuffer.getChannelData(0).slice());});audioProcessor.connect(audioContext.destination);}

録音した音声をバイナリデータ化

hogehoge.js
//音声をエクスポートした後のwavデータ格納用配列constwaveArrayBuffer=[];//仕様の関係で、大きなデータを分けたうちの1つのデータ容量が25MB以下になるよう制御if(audioData.length>250){constnum=audioData.length/250;constcount=Math.round(num);for(leti=0;i<count;i++){constsliceAudioData=audioData.slice(0,249);audioData.pop(0,249);constwaveData=exportWave(sliceAudioData);waveArrayBuffer.push(waveData);}}else{waveArrayBuffer.push(exportWave(audioData));}  //PHPへPOSTvaroReq=newXMLHttpRequest();oReq.open("POST",'任意のパス',true);oReq.onload=function(oEvent){// Uploaded.};//複数のデータをblob化するための配列constblob=[];//waveArrayBufferに入っている複数のデータを1つずつ配列に格納waveArrayBuffer.forEach(function(waveBuffer){blob.push(newBlob([waveBuffer],{type:'audio/wav'}));})varfd=newFormData();for(leti=0;i<blob.length;i++){fd.append('blob'+i,blob[i]);}// oReq.setRequestHeader('Content-Type','multipart/form-data; name="blob" boundary=\r\n');//配列ごとリクエスト送信oReq.send(fd);functionexportWave(audioData){// Float32Arrayの配列になっているので平坦化constaudioWaveData=flattenFloat32Array(audioData);// WAVEファイルのバイナリ作成用のArrayBufferを用意constbuffer=newArrayBuffer(44+audioWaveData.length*2);// ヘッダと波形データを書き込みWAVEフォーマットのバイナリを作成constdataView=writeWavHeaderAndData(newDataView(buffer),audioWaveData,sampleRate);returnbuffer;}// Float32Arrayを平坦化するfunctionflattenFloat32Array(matrix){constarraySize=matrix.reduce((acc,arr)=>acc+arr.length,0);letresultArray=newFloat32Array(arraySize);letcount=0;for(leti=0;i<matrix.length;i++){for(letj=0;j<matrix[i].length;j++){resultArray[count]=audioData[i][j];count++;}}returnresultArray;}// ArrayBufferにstringをoffsetの位置から書き込むfunctionwriteStringForArrayBuffer(view,offset,str){for(leti=0;i<str.length;i++){view.setUint8(offset+i,str.charCodeAt(i));}}// 波形データをDataViewを通して書き込むfunctionfloatTo16BitPCM(view,offset,audioWaveData){for(leti=0;i<audioWaveData.length;i++,offset+=2){lets=Math.max(-1,Math.min(1,audioWaveData[i]));view.setInt16(offset,s<0?s*0x8000:s*0x7FFF,true);}}// モノラルのWAVEヘッダを書き込むfunctionwriteWavHeaderAndData(view,audioWaveData,samplingRate){// WAVEのヘッダを書き込み(詳しくはWAVEファイルのデータ構造を参照)writeStringForArrayBuffer(view,0,'RIFF');// RIFF識別子view.setUint32(4,36+audioWaveData.length*2,true);// チャンクサイズ(これ以降のファイルサイズ)writeStringForArrayBuffer(view,8,'WAVE');// フォーマットwriteStringForArrayBuffer(view,12,'fmt ');// fmt識別子view.setUint32(16,16,true);// fmtチャンクのバイト数(第三引数trueはリトルエンディアン)view.setUint16(20,1,true);// 音声フォーマット。1はリニアPCMview.setUint16(22,1,true);// チャンネル数。1はモノラル。view.setUint32(24,samplingRate,true);// サンプリングレートview.setUint32(28,samplingRate*2,true);// 1秒あたりのバイト数平均(サンプリングレート * ブロックサイズ)view.setUint16(32,2,true);// ブロックサイズ。チャンネル数 * 1サンプルあたりのビット数 / 8で求める。モノラル16bitなら2。view.setUint16(34,16,true);// 1サンプルに必要なビット数。16bitなら16。writeStringForArrayBuffer(view,36,'data');// サブチャンク識別子view.setUint32(40,audioWaveData.length*2,true);// 波形データのバイト数(波形データ1点につき16bitなのでデータの数 * 2となっている)// WAVEのデータを書き込みfloatTo16BitPCM(view,44,audioWaveData);// 波形データreturnview;}

リクエスト受け取り部分(超絶一部抜粋)

hogehoge.php
//リクエスト受け取り$req=$_FILESvar_dump($req);//出力結果array(2){["blob0"]=>array(5){["name"]=>string(4)"blob"["type"]=>string(9)"audio/wav"["tmp_name"]=>string(14)"/tmp/ランダム文字列"["error"]=>int(0)["size"]=>int(509996)}

おわりに

ご指摘等ありましたら宜しくお願い致します!


Viewing all articles
Browse latest Browse all 8835

Trending Articles