Skip to content

음향계를 이용한 사용자 볼륨 측정

shinhyogeun edited this page Nov 28, 2021 · 4 revisions

화상통화에서 사용자가 말하는 동안에 사용자의 비디오 테두리를 파랗게 바꿔줘야하는 기능이 있었습니다. 다른 사람들의 소리 크기는 getSynchronizationSources라는 함수를 통해서 RTC과정에서 쉽게 구할 수 있었지만 정작 자신의 소리 크기를 구하는 방법이 찾기 쉽지 않아서 음향계를 만드는 코드를 참조하여 자신의 말소리 크기를 구현하고자 하였습니다. 저는 이번에 Web Audio API를 활용했습니다. Web Audio API는 웹에서 오디오에 이펙트를 추가하거나, 파형을 시각화 하는등 다양한 기능을 구현할 수 있도록 도와줍니다.

소리의 크기를 구하는 험난한 과정에 앞서서 AudioContext에 관한 지식이 필요합니다. 대략적인 설명을 해보겠습니다. 다음의 영상을 참조해도 좋습니다.
(https://www.youtube.com/watch?v=MY8LZ49D76c)

AudioContext를 이용하면 음성을 변조할 수도 있고 다양한 악기등 정말 디테일한 작업들을 할 수 있습니다. 그 과정들은 어떤 식으로 이루어질까요? 다음의 그림을 봐주세요.

오디오에 인풋이 들어오고 그것이 들리는과정에 Effect가 들어가네요! 즉 오디오인풋과 변조Effect를 연결하고 변조Effect를 최종 출력(Destination)과 연결함으로써 음성 변조가 일어나는 것입니다. 각각을 Node라고 봐도 무방합니다! 저는 이번 음향계를 이용하기 위해 위에와 같은 과정들과 비슷한 방향으로 진행했습니다. 코드와 함께 살펴보겠습니다.

    const audioContext = new AudioContext();
    const analyser = audioContext.createAnalyser();
    const microphone = audioContext.createMediaStreamSource(stream);
    const javascriptNode = audioContext?.createScriptProcessor(2048, 1, 1);
    analyser.smoothingTimeConstant = 0.2;
    analyser.fftSize = 1024;

    let check = true;
    const interval = 1000;

    microphone.connect(analyser);
    analyser.connect(javascriptNode);
    javascriptNode.connect(audioContext.destination);

첫번째 줄에서 AudioContext를 생성합니다. 음향관련 작업을 하기위해서 AudioContext를 사용하는 것은 필수입니다. 이를 통해 다양한 음향관련 Node들을 생성해 순서대로 배치할 수 있기 때문입니다. 두번째에서는 analyser을 생성합니다. 그리고 시작점인 마이크를 audioContext.createMediaStreamSource(stream)를 통해 생성합니다. createMediaStreamSource 함수는 WebRTC의 스트림에서 오디오를 추출해서 사용할 수 있게 해주는 함수입니다. 그리고 가장중요한 javascriptNode를 생성합니다. 이것은 다음의 그림을 보면서 설명하겠습니다. image
input과 output이 있고 이것이 javascriptNode를 들립니다. 그 안에서 다양한 분석이 이루어 질 수 있습니다. 저희의 볼륨측정도 여기서 이루어져야하는 것입니다! 그 후에는 마지막으로 Desitination으로 가면 모든 과정이 마무리됩니다. 그 후의 코드를 살펴보겠습니다.

 javascriptNode.onaudioprocess = () => {
      if (!check) return;
      const array = new Uint8Array(analyser.frequencyBinCount);
      analyser.getByteFrequencyData(array);

      const length = array.length;
      const values = array.reduce((acu, v) => acu + v, 0);
      const average = values / length;
      const minimumVolume = 30;
      if (Math.round(average) > minimumVolume) {
        myVideo.classList.add('saying');
        check = false;
        setTimeout(() => {
          check = true;
        }, interval);
      } else {
        myVideo.classList.remove('saying');
      }
    };
  } catch (e) {
    console.error(e);
  }

onaudioprocess함수는 input이 javascriptNode에 들어오면 실행되게 됩니다. 이때 analyser에서 주파수 정보를 받아 그것을 Unit8Array에 넣습니다. 그러면 주파수 정보가 배열에 입력이 된 상태입니다. 여기서 볼륨의 크기는 바로 이 배열의 원소들의 평균 값입니다. 그래서 이 배열원소의 평균값을 구했고 그것이 30보다 크다면 사용자가 말하고 있다는 것으로 정의하여 기능을 구현하였습니다. 하지만 이 상태는 매우 예민하게 작동합니다. 그래서 쓰로틀링을 걸어서 1초에 한번씩 진행되도록 구현하였습니다. (사용자가 말하면 테두리를 칠해주는데 그게 너무 예민하게 깜빡거리면 사용자 경험이 별로일 것 같아서 그랬습니다!🤔)

마지막으로 밑의 그림은 이번에 만든 로직을 시각화한 것입니다.

Clone this wiki locally