Как я могу извлечь предыдущий звук (с микрофона) в качестве буфера при обнаружении тишины (JS)?

Я использую Google Cloud API для преобразования речи в текст с серверной частью NodeJS. Приложение должно иметь возможность прослушивать голосовые команды и передавать их в серверную часть в виде буфера. Для этого мне нужно отправить буфер предыдущего звука при обнаружении тишины.

Любая помощь будет оценена по достоинству. Включая код js ниже

 if (!navigator.getUserMedia)
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||
        navigator.mozGetUserMedia || navigator.msGetUserMedia;

if (navigator.getUserMedia) {
    navigator.getUserMedia({audio: true}, success, function (e) {
        alert('Error capturing audio.');
    });
} else alert('getUserMedia not supported in this browser.');

var recording = false;

window.startRecording = function () {
    recording = true;
};

window.stopRecording = function () {
    recording = false;
    // window.Stream.end();
};

function success(e) {
    audioContext = window.AudioContext || window.webkitAudioContext;
    context = new audioContext();

    // the sample rate is in context.sampleRate
    audioInput = context.createMediaStreamSource(e);

    var bufferSize = 4096;
    recorder = context.createScriptProcessor(bufferSize, 1, 1);

    recorder.onaudioprocess = function (e) {
        if (!recording) return;
        console.log('recording');
        var left = e.inputBuffer.getChannelData(0);
        console.log(convertoFloat32ToInt16(left));
       };

    audioInput.connect(recorder);
    recorder.connect(context.destination);
}

person azhar    schedule 03.10.2017    source источник
comment
Я не понимаю, в чем вам нужна помощь: отправка данных в этот API? Обнаружить тишину? Разделить записанные данные?   -  person Kaiido    schedule 17.10.2017
comment
Отправляем данные в этот API и получаем вывод в реальном времени.   -  person azhar    schedule 17.10.2017
comment
@azhar Поддерживает ли API связь в реальном времени?   -  person guest271314    schedule 17.10.2017
comment
Ах, поэтому часть молчания на самом деле не является частью этого вопроса ... Документы, похоже, не говорят о прямых вызовах API из js, и это может быть не очень хорошей идеей, так как вы бы нужно, чтобы ваши жетоны были видны. О, но вы имеете в виду от node.js ??? Тогда просто следуйте документы, почему вы используете этот интерфейсный код?   -  person Kaiido    schedule 17.10.2017
comment
@Kaiido На самом деле я хочу обнаруживать речь с микрофона браузера, поэтому мне нужно отправить буфер из внешнего интерфейса.   -  person azhar    schedule 19.10.2017


Ответы (3)


Я не слишком уверен в том, что именно задается в вопросе, поэтому этот ответ предназначен только для того, чтобы дать возможность обнаруживать молчание в AudioStream.


Чтобы обнаружить тишину в AudioStream, вы можете использовать узел AudioAnalyser. , на котором вы будете вызывать метод getByteFrequencyData через равные промежутки времени и проверять, были ли звуки выше, чем ваш ожидаемый уровень в течение заданного времени.

Вы можете установить пороговый уровень напрямую с помощью свойства minDecibels AnalyserNode.

function detectSilence(
  stream,
  onSoundEnd = _=>{},
  onSoundStart = _=>{},
  silence_delay = 500,
  min_decibels = -80
  ) {
  const ctx = new AudioContext();
  const analyser = ctx.createAnalyser();
  const streamNode = ctx.createMediaStreamSource(stream);
  streamNode.connect(analyser);
  analyser.minDecibels = min_decibels;

  const data = new Uint8Array(analyser.frequencyBinCount); // will hold our data
  let silence_start = performance.now();
  let triggered = false; // trigger only once per silence event

  function loop(time) {
    requestAnimationFrame(loop); // we'll loop every 60th of a second to check
    analyser.getByteFrequencyData(data); // get current data
    if (data.some(v => v)) { // if there is data above the given db limit
      if(triggered){
        triggered = false;
        onSoundStart();
        }
      silence_start = time; // set it to now
    }
    if (!triggered && time - silence_start > silence_delay) {
      onSoundEnd();
      triggered = true;
    }
  }
  loop();
}

function onSilence() {
  console.log('silence');
}
function onSpeak() {
  console.log('speaking');
}

navigator.mediaDevices.getUserMedia({
    audio: true
  })
  .then(stream => {
    detectSilence(stream, onSilence, onSpeak);
    // do something else with the stream
  })
  .catch(console.error);

И в качестве скрипки, поскольку stackSnippets может блокировать gUM.

person Kaiido    schedule 17.10.2017

Вы можете использовать событие SpeechRecognition result, чтобы определить, когда слово или фраза была распознана, например, ls, cd, pwd или другие команды, передать .transcript из SpeechRecognitionAlternative в speechSynthesis.speak(), где в прикрепленных start и end событии SpeechSynthesisUtterance вызвать .start() или .resume() на MediaRecorder объекте, где передается MediaStream; преобразовать событие Blob at dataavailable в ArrayBuffer, используя FileReader или Response.arrayBuffer().

В качестве альтернативы мы могли бы использовать audiostart или soundstart с audioend или soundend событиями SpeechRecognition для записи фактического голоса пользователей, хотя концы могут не срабатывать последовательно по отношению к фактическому началу и концу звука, захваченного только стандартным системным микрофоном.

<!DOCTYPE html>
<html>

<head>
  <title>Speech Recognition Recording</title>
</head>

<body>
  <input type="button" value="Stop speech command recognition" id="stop">
  <script>
    navigator.mediaDevices.getUserMedia({
        audio: true
      })
      .then(stream => {
        const recorder = new MediaRecorder(stream);
        const recognition = new webkitSpeechRecognition();
        const synthesis = new SpeechSynthesisUtterance();
        const handleResult = e => {
          recognition.onresult = null;
          console.log(e.results);
          const result = e.results[e.results.length - 1];

          if (result.isFinal) {
            const [{transcript}] = result;
            console.log(transcript);
            synthesis.text = transcript;
            window.speechSynthesis.speak(synthesis);
          }
        }
        synthesis.onstart = () => {
          if (recorder.state === "inactive") {
            recorder.start()
          } else {
            if (recorder.state === "paused") {
              recorder.resume();
            }
          }
        }
        synthesis.onend = () => {
          recorder.pause();
          recorder.requestData();
        }
        recorder.ondataavailable = async(e) => {
          if (stream.active) {
            try {
              const blobURL = URL.createObjectURL(e.data);
              const request = await fetch(blobURL);
              const ab = await request.arrayBuffer();
              console.log(blobURL, ab);
              recognition.onresult = handleResult;
              // URL.revokeObjectURL(blobURL);
            } catch (err) {
              throw err
            }
          }
        }
        recorder.onpause = e => {
          console.log("recorder " + recorder.state);
        }
        recognition.continuous = true;
        recognition.interimResults = false;
        recognition.maxAlternatives = 1;
        recognition.start();
        recognition.onend = e => {
          console.log("recognition ended, stream.active", stream.active);

          if (stream.active) {
            console.log(e);
            // the service disconnects after a period of time
            recognition.start();
          }
        }
        recognition.onresult = handleResult;

        stream.oninactive = () => {
          console.log("stream ended");
        }

        document.getElementById("stop")
          .onclick = () => {
            console.log("stream.active:", stream.active);
            if (stream && stream.active && recognition) {
              recognition.abort();
              recorder.stop();
              for (let track of stream.getTracks()) {
                track.stop();
              }
              console.log("stream.active:", stream.active);
            }
          }

      })
      .catch(err => {
        console.error(err)
      });
  </script>
</body>

</html>

plnkr https://plnkr.co/edit/4DVEg6mhFRR94M5gdaIp?p=preview

person guest271314    schedule 15.10.2017
comment
webkitSpeechRecognition это должно работать только в хроме. Я хочу сделать это и в браузерах без Chrome. - person azhar; 15.10.2017
comment
Итак, я пытаюсь использовать api облака Google в бэкэнде и преобразовывать речь в текст. Но как транслировать голос из переднего конца в задний конец вживую - это моя проблема. - person azhar; 15.10.2017
comment
@azhar Я хочу сделать это и в браузерах без Chrome Один из подходов - использовать два элемента <button> для запуска или остановки записи MediaStream. Но как транслировать голос от внешнего интерфейса к другому в прямом эфире Вы пробовали использовать WebRTC? Требование, описанное в OP, - это передавать их в серверную часть в виде буфера, а не передавать MediaStream в реальном времени. - person guest271314; 15.10.2017
comment
Какой тип данных служба ожидает POSTed? - person guest271314; 15.10.2017
comment
Есть ли возможность с socket.io? - person azhar; 15.10.2017
comment
Как socket.io связано с требованиями? Самым простым решением было бы использовать кнопки для нажатия пользователем, когда звук должен быть записан и приостановлен, а затем POST записанный звук на сервер. Ожидает ли речь * oogle cloud api для текста ArrayBuffer в качестве входных данных? - person guest271314; 16.10.2017

Самый простой подход - использовать .pause() и .resume(), .stop() методы MediaRecorder(), чтобы позволить пользователю запускать, приостанавливать и останавливать запись звука, захваченного с использованием navigator.mediaDevices.getUserMedia(), и преобразовывать полученный Blob в ArrayBuffer, если это то, что API ожидает POST на сервер

<!DOCTYPE html>
<html>

<head>
  <title>User Media Recording</title>
</head>

<body>
  <input type="button" value="Start/resume recording audio" id="start">
  <input type="button" value="Pause recording audio" id="pause">
  <input type="button" value="Stop recording audio" id="stop">
  <script>
    navigator.mediaDevices.getUserMedia({
        audio: true
      })
      .then(stream => {
        const recorder = new MediaRecorder(stream);

        recorder.ondataavailable = async(e) => {
          if (stream.active) {
            try {
              const blobURL = URL.createObjectURL(e.data);
              const request = await fetch(blobURL);
              const ab = await request.arrayBuffer();
              // do stuff with `ArrayBuffer` of recorded audio
              console.log(blobURL, ab);
              // we do not need the `Blob URL`, we can revoke the object
              // URL.revokeObjectURL(blobURL);
            } catch (err) {
              throw err
            }
          }
        }
        recorder.onpause = e => {
          console.log("recorder " + recorder.state);
          recorder.requestData();
        }

        stream.oninactive = () => {
          console.log("stream ended");
        }

        document.getElementById("start")
          .onclick = () => {

            if (recorder.state === "inactive") {
              recorder.start();
            } else {
              recorder.resume();
            }
            console.log("recorder.state:", recorder.state);
          }

        document.getElementById("pause")
          .onclick = () => {

            if (recorder.state === "recording") {
              recorder.pause();
            }
            console.log("recorder.state:", recorder.state);
          }

        document.getElementById("stop")
          .onclick = () => {

            if (recorder.state === "recording" || recorder.state === "paused") {
              recorder.stop();
            }

            for (let track of stream.getTracks()) {
              track.stop();
            }

            document.getElementById("start").onclick = null;
            document.getElementById("pause").onclick = null;
            console.log("recorder.state:", recorder.state
            , "stream.active", stream.active);
          }

      })
      .catch(err => {
        console.error(err)
      });
  </script>
</body>

</html>

plnkr https://plnkr.co/edit/7caWYMsvub90G6pwDdQp?p=preview

person guest271314    schedule 17.10.2017