WEB/💡 Javascript

[Javascript] 마이크 변경과 볼륨 조절하기

무딘붓 2024. 3. 23. 22:12

 

 

마이크 볼륨 시각화하기 에 이어서, 마이크 변경과 오디오 볼륨을 조절하는 방법을 간단히 살펴봅니다.

 

바닐라 Javascript로 개발한 예제코드는 아래에서 확인할 수 있습니다.

https://codepen.io/vchgekmq-the-flexboxer/pen/poBWNbE

 

1. 입력 마이크 변경하기

입력 마이크를 변경하는 기능을 간단히 추가해 보겠습니다.

 

이전에 만든 볼륨 시각화 코드에서 이어서 개발할 예정이므로, 이전 게시글을 참고해 주시면 감사하겠습니다.

 

Javascript로 마이크 볼륨 시각화하기

기록으로 남기는 실시간 화이트보드 강의 서비스, 🙂 Boarlog 를 개발하며 구현한 내용을 담고 있습니다. 게시글의 기능을 이용한 최종 서비스는 GitHub 링크 에서 확인하실 수 있습니다. 웹 페이지

sirius7.tistory.com

 

우선 페이지에 간단한 <select> 태그를 추가해 줍니다.

<p>마이크 선택</p>
<select id="microphoneSelect"></select>

 

이제 아래와 같은 JavaScript코드를 추가해 봅시다.

let selectedMicrophone = null;

document.addEventListener("DOMContentLoaded", () => {
  const microphoneSelect = document.getElementById("microphoneSelect");

  navigator.mediaDevices
    .enumerateDevices()
    .then((devices) => {
      const microphoneDevices = devices.filter(
        (device) => device.kind === "audioinput"
      );
      microphoneDevices.forEach((device) => {
        const option = document.createElement("option");
        option.value = device.deviceId;
        option.textContent = device.label || `마이크 ${microphoneDevices.indexOf(device) + 1}`;
        microphoneSelect.appendChild(option);
      });
    })
    .catch((error) => {
      console.error("불러오기 실패", error);
    });

  microphoneSelect.addEventListener("change", async (e) => {
    selectedMicrophone = e.target.value;
    if (audioStream) audioStream.getTracks().forEach((track) => track.stop()); // 기존 미디어 트랙 중지
    startRecording();
  });
});

 

코드를 차근차근 확인해 보겠습니다.

  • document.addEventListener("DOMContentLoaded", () => {
    • DOM 트리를 완성하는 즉시 실행합니다.
  • const microphoneSelect = document.getElementById("microphoneSelect");
    • 앞서 만든 <select> 태그의 참조를 저장합니다.
  • navigator.mediaDevices.enumerateDevices()
    • 현재 사용 가능한 미디어 입력 및 출력 장치 목록을 불러옵니다.
  • const microphoneDevices = devices.filter( (device) => device.kind === "audioinput" );
    • 장치 목록에서 오디오 입력 장치인 마이크만 필터링해서 microphoneDevices에 저장합니다.
  • microphoneDevices.forEach((device) => { ~ }
    • microphoneDevices의 각 마이크를 <option>으로 만들고, <select> 태그에 추가합니다.
  • microphoneSelect.addEventListener("change", ~
    • <select> 태그에서 선택된 내용이 바뀌면 실행됩니다.
  • selectedMicrophone = e.target.value;
    • 현재 선택된 마이크의 id를 selectedMicrophone에 저장합니다.
  • if (audioStream) audioStream.getTracks().forEach((track) => track.stop());
    • 기존의 미디어 트랙 중지합니다.
  • startRecording();
    • 앞선 게시글에서 구현한 볼륨 시각화를 다시 실행시킵니다.

 

요약하면, navigator.mediaDevices.enumerateDevices()으로 전체 미디어 장치 목록을 불러오고,

그중에서 오디오 입력장치만 필터링해서 <select> 태그의 option으로 추가해 줍니다. 

 

그런 다음, <select>에서 오디오를 선택하면 해당 오디오의 id를 selectedMicrophone에 저장합니다.

이제 볼륨 시각화 코드를 다음과 같이 수정합니다.

const startRecording = () => {
  navigator.mediaDevices
    .getUserMedia({ audio: { deviceId: selectedMicrophone } })
    .then((stream) => {
	    
	    // (이전과 동일)
    
    })
    .catch((error) => {
      console.error("마이크 권한 획득 실패", error);
    });
};

 

여기서 변경된 곳은 getUserMedia()에서 audio의 deviceId를 selectedMicrophone로 지정하는 부분입니다.

이와 같은 방법으로 getUserMedia()에서 특정한 장치에서 입력되는 미디어를 지정해서 가져올 수 있습니다.

 

 

2. 입력 오디오 볼륨 조절하기

이제 사용자의 마이크로 입력받은 오디오의 볼륨 크기를 조절해 보겠습니다.

 

오디오의 크기를 조절하기 위해서는 Web Audio API의 GainNode를 사용합니다.

GainNode는 오디오 신호의 볼륨을 조절하는 데 사용하는 인터페이스로, 자세한 설명은 아래 링크에서 확인할 수 있습니다.

 

https://developer.mozilla.org/en-US/docs/Web/API/GainNode

 

GainNode - Web APIs | MDN

The GainNode interface represents a change in volume. It is an AudioNode audio-processing module that causes a given gain to be applied to the input data before its propagation to the output. A GainNode always has exactly one input and one output, both wit

developer.mozilla.org

 

먼저 아래처럼 <input> 태그를 이용해서 간단한 슬라이더 바를 만들어 줍니다.

<p>마이크 볼륨</p>
<input id="volumeSlider" class="w-full" type="range" min="0" max="1" step="0.01">

 

이제 볼륨 값의 가중치를 저장할 micVolume를 선언하고, 아래와 같이 슬라이더 바의 값이 변경되었을 때, micVolume가 갱신되는 코드를 작성해 줍니다.

 

let micVolume = 0.5;

document.addEventListener("DOMContentLoaded", () => {
  const volumeSlider = document.getElementById("volumeSlider");
  
	/*
	(앞선 코드와 동일
	*/
	
  volumeSlider.addEventListener("change", (e) => {
    micVolume = parseFloat(e.target.value);
  });
}

이제 본격적으로 GainNode를 이용하여 마이크 볼륨값을 조절해 보겠습니다.

micVolume에 따라 마이크 볼륨이 조절되도록 수정된 startRecording의 코드를 미리 보면 다음과 같습니다.

const startRecording = () => {
  navigator.mediaDevices
    .getUserMedia({ audio: { deviceId: selectedMicrophone } })
    .then((stream) => {
      isRecording = true;
      audioStream = stream;

      // Web Audio API에서 오디오를 다루기 위한 기본 객체 AudioContext 생성
      const audioContext = new AudioContext();
      // 오디오 신호를 분석하기 위한 AnalyserNode 객체 생성
      const analyser = audioContext.createAnalyser();
      // 미디어 스트림을 audioContext 내에서 사용할 수 있는 형식으로 변환
      const mediaStreamAudioSourceNode =
        audioContext.createMediaStreamSource(stream);

      // 입력 오디오 신호의 볼륨을 조절하기 위한 GainNode 객체 생성
      const gainNode = audioContext.createGain();
      // 변환된 미디어 스트림을 GainNode 객체에 연결
      mediaStreamAudioSourceNode.connect(gainNode);
      // GainNode 객체를 AnalyserNode 객체에 연결
      gainNode.connect(analyser);

      const pcmData = new Float32Array(analyser.fftSize);
      const onFrame = () => {
        gainNode.gain.value = micVolume;

				// 여기서부터는 기존과 동일 (볼륨 시각화 코드)
        analyser.getFloatTimeDomainData(pcmData);
        let sum = 0.0;
        for (const amplitude of pcmData) {
          sum += amplitude * amplitude;
        }
        const rms = Math.sqrt(sum / pcmData.length);
        const normalizedVolume = Math.min(1, rms / 0.5);
        colorVolumeMeter(normalizedVolume * 2);
        onFrameId = window.requestAnimationFrame(onFrame);
      };

      onFrameId = window.requestAnimationFrame(onFrame);
    })
    .catch((error) => {
      console.error("마이크 권한 획득 실패", error);
    });
};

 

여기서 GainNode로 변경된 부분만 따로 확인해 보겠습니다.

// 입력 오디오 신호의 볼륨을 조절하기 위한 GainNode 객체 생성
const gainNode = audioContext.createGain();
// 변환된 미디어 스트림을 GainNode 객체에 연결
mediaStreamAudioSourceNode.connect(gainNode);
// GainNode 객체를 AnalyserNode 객체에 연결
gainNode.connect(analyser);

const pcmData = new Float32Array(analyser.fftSize);
const onFrame = () => {
  // GainNode의 gain값을 갱신
  gainNode.gain.value = micVolume;  
  
  // ...
}

 

우선 GainNode의 주요 속성은 'gain'을 살펴보겠습니다.

 

gain 속성은 해당 노드를 통과하는 오디오 신호의 볼륨을 제어하는 속성입니다. gain의 value는 0~1 사이의 값을 가지는데, 0은 무음, 1은 오디오 신호가 그대로 전달됨을 의미합니다.

이러한 gain 속성을 가지는 GainNode를 오디오 소스 노드와 출력 노드 사이에 추가해서 볼륨을 조절할 수 있는데, 이를 위해서 위의 코드는 아래와 같이 구현되어 있습니다.

 

  • 1) GainNode를 생성
  • 2) 오디오 분석을 위해 변환한 mediaStreamAudioSourceNode를 GainNode에 연결
  • 3) GainNode를 AnalyserNode 객체에 연결

 

이런 과정을 통해 볼륨을 조절할 수 있습니다. 더 이해하기 쉽게 동작 과정을 그림으로 나타내면 다음과 같습니다.

 

 

지금까지의 코드를 정리하면 아래와 같습니다.

 

 

See the Pen 마이크 변경과 볼륨 조절 예제 by 주완 (@vchgekmq-the-flexboxer) on CodePen.