Successfully reported this slideshow.
Your SlideShare is downloading. ×

we are javascript LTの資料

Ad

汎用的なオンラインビデオ会議
システムを作る
森山雄太

Ad

DEMO

Ad

ビデオ会議システム+メディア
で必要な機能
• ビデオで通話を行う
– ビデオ、音声のみ、snapshotコマ送りの切り替え
• 録音
• 会話の文字起こし
• チャット
• ユーザ間同期
• 部屋への入室

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Check these out next

1 of 18 Ad
1 of 18 Ad

More Related Content

we are javascript LTの資料

  1. 1. 汎用的なオンラインビデオ会議 システムを作る 森山雄太
  2. 2. DEMO
  3. 3. ビデオ会議システム+メディア で必要な機能 • ビデオで通話を行う – ビデオ、音声のみ、snapshotコマ送りの切り替え • 録音 • 会話の文字起こし • チャット • ユーザ間同期 • 部屋への入室
  4. 4. 今日は録音にFOCUS
  5. 5. もっと良いやり方があったら 教えてもらいたいから
  6. 6. 録音ユースケース • ユーザがクリックしたら録音開始 • 終了で録音終了 • 複数のユーザが順番に喋るので結合する。 – 同時にはしゃべらない • 録音ファイルはS3に保存し、後ほど聞 くことができるようにする。
  7. 7. Socket.io- stream (1)AudioAPI (2)Binary変換 (3)wav.file writerで保存 (4)Sample Rate統一 (5)複数ユーザの音声ファイル結合 (6) mp3化 (7)S3アップロード (8)Firebaseへ登録
  8. 8. 音声データ 音声データ波形 音声データ波形の簡略化図 16 bit stereo sample data 44khz マイクから取得した32bit stereoデータを16bitモノラルデータにして サーバに送信して格納保存したい。 ステレオ 左右別々の音 モノラル 一つの音
  9. 9. Browserでの処理 const promise = navigator.mediaDevices.getUserMedia ({ audio:true,video: false}); promise.then( (audio_stream)=>{ Streamの取得 音声binaryデータの取得 const audio_context = new AudioContext(); this.sample_rate_value = audio_context.sampleRate; const audioInput = audio_context.createMediaStreamSource(audio_stream); const bufferSize = 4096; const scriptNode = audio_context.createScriptProcessor(bufferSize, 1, 1); audioInput.connect(scriptNode); scriptNode.connect(audio_context.destination); scriptNode.onaudioprocess = (audioProcessingEvent)=>{ var left = audioProcessingEvent.inputBuffer.getChannelData(0); var audio_array_buffer = this.convertoFloat32ToInt16(left);
  10. 10. Browserでの処理 convertoFloat32ToInt16 = (buffer)=>{ const len = buffer.length; const double_len = len*2; const unit8_buf = new Uint8Array(double_len); const int16_variable =ç new Int16Array(1); for (let i=0; i< len; i++) { int16_variable[0] = buffer[i]*0x7FFF; //convert to 16 bit PCM unit8_buf[2*i] = int16_variable[0] & 0x00FF; //convert to uint8 for stream buffer unit8_buf[2*i+1] = (int16_variable[0] & 0xFF00) >> 8; } return unit8_buf.buffer; } Float32ArrayのAudioデータを 16bit PCMデータに変換し、Uint8Array型で返す Socket.streamでサーバへ送信 var audio_array_buffer = this.convertoFloat32ToInt16(left); this.socket_stream.stream_record_process(audio_array_buffer);
  11. 11. Browserでの処理 convertoFloat32ToInt16 = (buffer)=>{ const len = buffer.length; const double_len = len*2; const unit8_buf = new Uint8Array(double_len); const int16_variable = new Int16Array(1); for (let i=0; i< len; i++) { int16_variable[0] = buffer[i]*0x7FFF; //convert to 16 bit PCM unit8_buf[2*i] = int16_variable[0] & 0x00FF; //convert to uint8 for stream buffer unit8_buf[2*i+1] = (int16_variable[0] & 0xFF00) >> 8; } return unit8_buf.buffer; } Float32ArrayのAudioデータを 16bit PCMデータに変換し、Uint8Array型で返す Socket.streamでサーバへ送信 var audio_array_buffer = this.convertoFloat32ToInt16(left); this.socket_stream.stream_record_process(audio_array_buffer);
  12. 12. Binary変換の詳細 ScriptNodeからは、 Float32Arrayで 音声データを受け取る。 -0.999… 〰 0.9999… の浮動小数点データ uint8Arrayに変換し、 サーバになげる。 int16_variable[0] = buffer[i]*0x7FFF; -32766〰 32766の16byte PCM16データ に変換 http://webdemo.dac.co.jp/mixidea/prestudy/convertoFloat32ToInt16.html http://webdemo.dac.co.jp/mixidea/prestudy/typed_array_invest.html
  13. 13. Socket stream送信 this.socket_io = io.connect(this.socket_url) this.stream = ss.createStream(); ss(this.socket_io).emit('audio_record_start', this.stream, start_emit_obj );z Connectionおよび、strem送信の開始 送信終了 this.stream.end(); this.socket_io.emit('audio_record_end', stop_emit_obj); stream_record_process(audio_array_buffer){ const stream_buffer = new ss.Buffer(audio_array_buffer); this.stream.write(stream_buffer, 'buffer'); } Binaryデータの送信 送信一時中断 this.stream.end(); this.socket_io.emit('audio_record_suspend',);
  14. 14. サーバでの処理 mixidea_io.on('connection',(socket)=>{ ss(socket).on('audio_record_start', (stream, data)=>{ eval("GlobalInfo.file_writer_count_" + outfile_name + "=1"); var file_writer = new wav.FileWriter( outfile_name_wav, {channels:1, sampleRate:sample_rate, bitDepth:16 } ); stream.pipe(file_writer); GlobalInfo = {} ユーザ間での情報交換のためのGlobal変数定義 Audioデータの取得とfileへの書き込み Streamをfile writerにつなげる。 Global 変数に、 File保存時の情報を格納
  15. 15. サーバでの処理 Streamの一時停止 socket.on('audio_record_suspend', function(data){ Streamの終了 socket.on('audio_record_end', function(data){ setTimeout(function(){ next_func(); }, record_duration); SamplilngRateの変更 var command = SoxCommand().output(wstream).outputFileType('wav').outputSampleRate(44100); command.input(existing_file_name); command.on('end', function() { 何もせず Audio_recordが終わったという通知が きても、 File_writerの処理はしばらく続くので、 一定時間待ってから次の処理へいく 各ユーザの録音環境により、 Sample rateが異なるため、結合する前 に Sample rateをそろえる。
  16. 16. サーバでの処理 複数のAudioファイルの合成 eval(" file_list_len = GlobalInfo.file_writer_count_" + outfile_name ); for(var i=0; i< file_list_len; i++){ var each_file_name = './' + file_name + "_"+ String(i+1) + ".wav"; command.input(each_file_name); } command.on('end') mp3への変換 const wstream = fs.createWriteStream(dest_file); const command = SoxCommand().output(wstream).outputFileType('mp3'); command.on('end', function() { 各ユーザの録音環境により、 Sample rateが異なるため、結合す る前に Sample rateをそろえる。 Global 変数から、保存されている ファイルの情報取得
  17. 17. サーバでの処理 S3へのアップロード fs.readFile(dest_file, function (err, data) { s3.putObject( {Key: file_name_on_s3, ContentType: "audio/mp3", Body: data, ACL: "public-read"}, function(error, data){ firebaseへの登録 const database = firebase_admin.database(); database.ref(child_path).set(file_path, (error)=>{
  18. 18. 知りたいこと • 各ユーザ間のデータ共有に、global変数を用いているが、この 方法は適切だろうか? • Filewriter にStreamとしてデータがpipeでつなげるが、一時停 止や、処理を終了せずに放置された場合、サーバ側ではメモ リリークなどがおきているのか? • 同時に何人くらいの録音が可能だろうか?

×