개발

[React] 음원, 녹음 동시 작업 실행 시, 녹음 품질 저하 문제 해결 및 고찰

1223v 2024. 9. 23. 02:10

 

문제 인식


통번역 플랫폼 개발 진행 중, 음원과 녹음 동시 작업 실행 시, 녹음의 품질이 저하되는 것을 발견했습니다.

이에 해결과정과 코드를 공유하고자 합니다.

 

기존 코드의 문제점은 다음으로 추측했었습니다.


 

문제점 종합


1. recorderDestination와 audioContext가 컴포넌트가 리렌더링될 때마다 새로 생성됨

audioContext와 recorderDestination이 컴포넌트가 리렌더링될 때마다 새로 생성됩니다. React 컴포넌트는 상태 변화로 인해 여러 번 리렌더링될 수 있는데, 이때마다 새로운 AudioContext와 MediaStreamDestination이 생성되어 기존의 오디오 스트림이 무효화되거나 상태를 유지하지 못할 수 있습니다.

 

2. 비동기 처리 문제

navigator.mediaDevices.getUserMedia는 비동기로 마이크 스트림을 가져오는데, 이 작업이 완료되기 전에 녹음이 시작될 수 있습니다. 이로 인해 스트림이 아직 연결되지 않은 상태에서 녹음이 진행되거나 오류가 발생할 수 있을 것입니다.

 

3. 오디오 피드백 방지를 위한 오디오 이득 제어(AGC), 노이즈 억제 문제

일부 브라우저나 시스템에서는 자동 이득 제어 기능이 활성화되어 음원 볼륨이 커지면 마이크 입력이 자동으로 낮아지는 현상이 발생할 수 있을 것입니다.

 

4. recorderDestination.stream에 접근할 때 초기화되지 않았을 가능성

recorderDestination.stream에 접근하는 코드가 있지만, recorderDestination이 아직 초기화되지 않았을 경우 null 오류가 발생할 수 있을 것입니다.

 

5. 마이크 스트림과 오디오 재생 간의 상호작용 문제

음원이 재생되는 동안 마이크 스트림이 제대로 녹음되지 않을 수 있습니다. 이는 마이크에서 음원이 다시 입력되거나 피드백이 발생할 가능성이 있기 때문입니다. 특히 볼륨이 클 때 녹음이 작아지는 현상이 발생할 수 있습니다.

 

 

위 요인들이 모두가 녹음 품질에 영향을 주었을 것이라 생각되지 않습니다만 우선, 위 5가지 문제의 코드 개선을 통한 녹음 품질은 어느정도 개선이 되어 공유합니다.


 

해결 과정


1번 문제 해결 방법

recorderDestination와 audioContext가 컴포넌트가 리렌더링될 때마다 새로 생성되는 문제

const audioContextRef = useRef(null);
const recorderDestinationRef = useRef(null);

 

useRef를 사용하여 audioContextRef와 recorderDestinationRef로 변경하였습니다. 이로 인해 컴포넌트가 리렌더링될 때도 상태를 유지하면서, 같은 오디오 컨텍스트와 스트림을 사용할 수 있게 했습니다.


 

2번 문제 해결 방법

비동기 처리 문제

이전 코드

 useEffect(() => {
    navigator.mediaDevices.getUserMedia({ audio: true })
      .then(stream => {
        const microphone = audioContext.createMediaStreamSource(stream);
        microphone.connect(recorderDestination);  // 녹음 스트림만 처리
      })
      .catch(error => {
        console.error('마이크 입력을 가져오는데 실패했습니다:', error);
      });
  }, []);

마이크 스트림이 navigator.mediaDevices.getUserMedia를 통해 가져와지면 바로 처리되었지만, 초기화 과정에서 지연이 발생할 수 있었습니다.

 

개선된 코드

const initializeAudioContext = async () => {
      audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)();
      recorderDestinationRef.current = audioContextRef.current.createMediaStreamDestination();

      // 마이크 스트림을 미리 활성화
      try {
        const stream = await navigator.mediaDevices.getUserMedia({
          audio: {
            echoCancellation: true,      // 에코 제거
            noiseSuppression: true,      // 노이즈 억제
            autoGainControl: false       // 자동 이득 제어(AGC) 비활성화
          }
        });

        const microphone = audioContextRef.current.createMediaStreamSource(stream);


      } catch (error) {
        console.error('마이크 입력을 가져오는 데 실패했습니다:', error);
      }
    };

    initializeAudioContext();
  }, []);

 

useEffect 내에서 initializeAudioContext라는 비동기 함수로 오디오 컨텍스트와 마이크 스트림을 초기화하도록 변경되었습니다. 이를 통해 마이크 초기화가 완료된 후 필터 및 게인 노드를 설정하고, 녹음에 필요한 모든 오디오 처리가 완료되도록 보장했습니다.

 


3번 문제 해결 방법

오디오 피드백 방지를 위한 오디오 이득 제어(AGC), 노이즈 억제 문제

이전 코드

{ audio: true }

브라우저에 오디오 권한을 일임함으로서 녹음 품질이 저하될 수 있었습니다.

 

개선된 코드

audio: {
    echoCancellation: true,  // 에코 제거
    noiseSuppression: true,  // 노이즈 제거
    autoGainControl: false   // 자동 이득 제어 비활성화
  }

동시에 음성이 줄어드는 문제는 노이즈 억제 설정의 문제일 수도  위 코드는 echoCancellation 및 noiseSuppression을 활성화하면서도 자동 이득 제어(AGC)를 비활성화하여 음원이 마이크에 영향을 덜 미치도록 했습니다.

 


 

4번 문제 해결 방법

recorderDestination.stream에 접근할 때 초기화되지 않았을 가능성

 

이전 코드

mediaStream: recorderDestination.stream // 녹음 스트림 연결

이전 코드에서는 recorderDestination.stream에 바로 접근하려고 했지만, recorderDestination이 아직 초기화되지 않은 상태에서 접근할 경우 오류가 발생할 수 있었습니다. 이러한 문제는 스트림이 완전히 준비되지 않은 상태에서 녹음을 시작하게 되어 품질 저하로 이어질 수 있습니다.

 

개선된 코드

// recorderDestinationRef.current가 null이 아닌지 확인
mediaStream: recorderDestinationRef.current ? recorderDestinationRef.current.stream : null

recorderDestinationRef.current가 초기화된 후에만 mediaStream에 접근하도록 조건을 추가하여 이러한 문제를 해결했습니다. 이를 통해 스트림이 준비되지 않은 상태에서 녹음하는 것을 방지할 수 있습니다.

 

 


5번 문제 해결 방법

마이크 스트림과 오디오 재생 간의 상호작용 문제

 

이전 코드

 // 마이크 입력 처리
  useEffect(() => {
    navigator.mediaDevices.getUserMedia({ audio: true })
      .then(stream => {
        const microphone = audioContext.createMediaStreamSource(stream);
        microphone.connect(recorderDestination);  // 녹음 스트림만 처리
      })
      .catch(error => {
        console.error('마이크 입력을 가져오는데 실패했습니다:', error);
      });
  }, []);

음원 재생 중 마이크 스트림이 음원을 다시 녹음하거나, 음원의 높은 볼륨이 마이크 녹음 품질에 영향을 주어 음성이 작아지는 등의 피드백 문제가 발생할 수 있습니다. 특히 음원이 크게 재생될 때 마이크 녹음이 왜곡될 수 있는 문제는 녹음 품질에 직접적인 영향을 줍니다.

 

 

개선된 코드

// Web Audio API 설정
  useEffect(() => {
    const initializeAudioContext = async () => {
      audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)();
      recorderDestinationRef.current = audioContextRef.current.createMediaStreamDestination();

      // 마이크 스트림을 미리 활성화
      try {
        const stream = await navigator.mediaDevices.getUserMedia({
          audio: {
            echoCancellation: true,      // 에코 제거
            noiseSuppression: true,      // 노이즈 억제
            autoGainControl: false       // 자동 이득 제어(AGC) 비활성화
          }
        });

        const microphone = audioContextRef.current.createMediaStreamSource(stream);

        // 마이크 볼륨 조절용 GainNode
        const micGain = audioContextRef.current.createGain();
        micGain.gain.value = 1.5; // 마이크 입력 볼륨 증폭

        // Notch 필터 적용 (특정 주파수 대역 제거)
        const notchFilter = audioContextRef.current.createBiquadFilter();
        notchFilter.type = 'notch';
        notchFilter.frequency.value = 440; // 필요에 따라 음원 주파수에 맞춰 설정
        notchFilter.Q.value = 10; // 필터 대역폭 설정

        // Lowpass 필터 추가 (고주파수 제거)
        const lowpassFilter = audioContextRef.current.createBiquadFilter();
        lowpassFilter.type = 'lowpass';
        lowpassFilter.frequency.value = 1000; // 필요에 따라 조정

        // Compressor 추가 (다이내믹 레인지 압축)
        const compressor = audioContextRef.current.createDynamicsCompressor();
        compressor.threshold.setValueAtTime(-50, audioContextRef.current.currentTime); // 클리핑 방지
        compressor.knee.setValueAtTime(40, audioContextRef.current.currentTime); // 부드러운 압축
        compressor.ratio.setValueAtTime(12, audioContextRef.current.currentTime); // 압축 비율
        compressor.attack.setValueAtTime(0, audioContextRef.current.currentTime); // 빠른 반응 시간
        compressor.release.setValueAtTime(0.25, audioContextRef.current.currentTime); // 빠른 해제 시간

        // 마이크 -> Notch 필터 -> Lowpass 필터 -> Compressor -> Gain -> 녹음 스트림
        microphone.connect(notchFilter);
        notchFilter.connect(lowpassFilter);
        lowpassFilter.connect(compressor);
        compressor.connect(micGain);
        micGain.connect(recorderDestinationRef.current); // 녹음 스트림으로 연결
      } catch (error) {
        console.error('마이크 입력을 가져오는 데 실패했습니다:', error);
      }
    };

    initializeAudioContext();
  }, []);

개선된 코드에서는 GainNode, Notch Filter, Lowpass Filter, Compressor 등을 적용하여 오디오 신호를 조절했습니다. GainNode로 마이크 입력 볼륨을 증폭하고, Notch Filter로 특정 주파수 대역을 제거하며, Lowpass Filter로 고주파수를 줄이고, Compressor로 다이내믹 레인지를 조정하여 오디오 품질을 개선했습니다.

 

  • Notch 필터
    특정 주파수 대역(예: 특정 톤의 잡음)을 제거하여, 음원의 간섭이나 잡음이 녹음에 영향을 미치지 않도록 했습니다.

    notchFilter.type = 'notch' : 특정 주파수 대역을 제거하는 노치 필터를 사용합니다.

    notchFilter.frequency.value = 440 : 440Hz는 음악에서 'A4'로 알려진 음입니다.이 주파수를 제거하려는 이유는 예시일 뿐이며, 실제로는 제거하려는 특정 주파수 대역에 맞춰 값을 조정해야 합니다.

    notchFilter.Q.value = 10 : Q 값은 필터의 대역폭을 조정합니다. 값이 클수록 더 좁은 대역을 필터링합니다. Q = 10은 중간 정도의 대역폭으로, 주파수 대역을 지나치게 넓게 잡지 않고 필요한 부분만 제거하기 위한 설정입니다.

 

  • Lowpass 필터
    고주파 잡음을 제거하여, 지나치게 높은 주파수의 소음이 녹음에 섞이는 것을 방지했습니다.

    lowpassFilter.type = 'lowpass': 고주파수를 제거하고 저주파수를 통과시키는 로우패스 필터를 사용합니다.

    lowpassFilter.frequency.value = 1000: 이 설정은 1,000Hz 이상의 고주파를 제거합니다. 보통 인간의 음성 주파수는 85Hz에서 255Hz 정도이므로, 1,000Hz 이상을 제거함으로써 음성에 영향을 주지 않으면서 불필요한 고주파 잡음을 차단할 수 있습니다.

    1,000Hz 이상의 주파수는 고주파 잡음으로 간주하고 제거합니다. 이는 음성 녹음 시 음성에 영향을 미치지 않으면서 고주파 소음을 줄이는 데 적합합니다.

 

  • Compressor
    오디오 신호의 다이내믹 레인지를 줄여, 너무 큰 소리는 줄이고 너무 작은 소리는 키워서, 일정한 볼륨을 유지하게 했습니다.
    즉, 소리의 크기가 너무 커지면 이를 줄이고, 작은 소리는 상대적으로 증폭하여 소리가 일정한 볼륨 수준으로 유지되도록 만드는 기술입니다.

    compressor.threshold.setValueAtTime(-50, audioContextRef.current.currentTime): 임계값을 -50dB로 설정하여, 이보다 큰 소리는 압축합니다. 임계값을 낮게 설정한 이유는 녹음 중 갑작스럽게 커지는 소리를 방지하기 위해서입니다.

    compressor.knee.setValueAtTime(40, audioContextRef.current.currentTime): Knee 값은 압축이 부드럽게 시작되는 정도를 설정합니다. 값이 클수록 압축이 점진적으로 시작됩니다. 40을 통해 부드러운 압축을 진행합니다.

    compressor.ratio.setValueAtTime(12, audioContextRef.current.currentTime): 압축 비율은 소리가 임계값을 넘을 때 얼마나 압축할지 결정합니다. 비율이 12:1이면, 임계값을 넘는 소리는 12배로 줄여서 압축됩니다.

    compressor.attack.setValueAtTime(0, audioContextRef.current.currentTime): 공격 시간은 압축이 적용되기 시작하는 시간입니다. 0으로 설정하면 즉시 압축이 시작됩니다.

    compressor.release.setValueAtTime(0.25, audioContextRef.current.currentTime): 릴리즈 시간(Release)은 압축이 해제되는 시간입니다. 0.25초로 설정하여 빠르게 압축을 해제합니다.


  • GainNode (Mic Gain)
    마이크 입력 볼륨을 증폭하여 소리가 작게 녹음되는 문제를 해결했습니다.

    micGain.gain.value = 1.5:마이크 볼륨을 1.5배로 증폭합니다. 이 값은 마이크 입력이 너무 작을 때 이를 보정하기 위해 설정됩니다.

 


기존 코드

import { message } from "antd";
import Axios from "axios";
import React, { useContext, useEffect, useState } from "react";
import AudioAnalyser from "react-audio-analyser";
import { useLocation } from "react-router-dom";
import MP from "../../../../assets/sound/MP.mp3";
import EffectSound from "../../../../util/EffectSound";
import { API_URL } from "../../../Config";
import { MainContext } from "./MainContext";
import RecordButton from "./RecordButton";

export default function SimAudioRecorderFunc(props) {
  const location = useLocation();
  const params = new URLSearchParams(location.search);
  const asNo = params.get("as_no");
  const { audioURL, setAudioURL, setAudioExtension } = useContext(MainContext);
  const [status, setStatus] = useState("");
  const [audioSrc, setAudioSrc] = useState("");
  const [audioType, setAudioType] = useState("audio/mp3");
  const [shouldHide, setshouldHide] = useState(false);
  
  // Web Audio API 관련 설정
  const audioContext = new (window.AudioContext || window.webkitAudioContext)();
  const recorderDestination = audioContext.createMediaStreamDestination();

  // 마이크 입력 처리
  useEffect(() => {
    navigator.mediaDevices.getUserMedia({ audio: true })
      .then(stream => {
        const microphone = audioContext.createMediaStreamSource(stream);
        microphone.connect(recorderDestination);  // 녹음 스트림만 처리
      })
      .catch(error => {
        console.error('마이크 입력을 가져오는데 실패했습니다:', error);
      });
  }, []);

  const Mp = EffectSound(MP, 1);

  const controlAudio = (status) => {
    setStatus(status);
  };

  const toggleRecording = () => {
    status === "recording"
      ? controlAudio("inactive")
      : controlAudio("recording");
  };

  const audioProps = {
    audioType,
    status,
    audioSrc,
    timeslice: 1000,
    startCallback: (e) => {
      console.log("succ start", e);
    },
    pauseCallback: (e) => {
      console.log("succ pause", e);
    },
    stopCallback: (e) => {
      setAudioSrc(window.URL.createObjectURL(e));
      console.log("succ stop", e);
      setAudioURL(window.URL.createObjectURL(e));

      const formData = new FormData();
      formData.append("assignment", asNo);
      formData.append("mp3", e);
      Axios.put(${API_URL}api/stt, formData, {
        headers: {
          "Content-Type": "multipart/form-data",
        },
        withCredentials: true,
      })
        .then((response) => {
          message.success("임시저장 완료");
          props.setSubmitlist(response.data.file);
          props.setTemporarySubmitCheck(true);
        })
        .catch((error) => {
          console.error("파일 업로드 실패:", error);
        });
    },
    onRecordCallback: (e) => {
      console.log("recording", e);
    },
    errorCallback: (err) => {
      console.log("error", err);
    },
    mediaStream: recorderDestination.stream // 녹음 스트림 연결
  };

  const onRecordCheck = () => {
    toggleRecording();
    setshouldHide(false);
    props.setLoading(true);
  };

  useEffect(() => {
    setAudioExtension(audioType.replace("audio/", ""));
  }, []);

  useEffect(() => {
    setAudioExtension(audioType.replace("audio/", ""));
  }, [setAudioExtension, audioType]);

  useEffect(() => {
    if (props.Startmusic && props.Disable === props.region_index) {
      toggleRecording();
      props.setStartmusic(false);
      setshouldHide(true);
      console.log("녹음 시작");
      Mp.play();
    }
  }, [props.Startmusic]);

  return (
    <div style={{ margin: "10px" }}>
      <AudioAnalyser {...audioProps} width={290}>
        {shouldHide && (
          <div className="btn-box">
            <RecordButton id="recordButton" onClick={onRecordCheck} />
          </div>
        )}
      </AudioAnalyser>
    </div>
  );
}

 

개선된 전체 코드

import { message } from "antd";
import Axios from "axios";
import React, { useContext, useEffect, useRef, useState } from "react";
import AudioAnalyser from "react-audio-analyser";
import { useLocation } from "react-router-dom";
import MP from "../../../../assets/sound/MP.mp3";
import EffectSound from "../../../../util/EffectSound";
import { API_URL } from "../../../Config";
import { MainContext } from "./MainContext";
import RecordButton from "./RecordButton";

export default function SimAudioRecorderFunc(props) {
  const location = useLocation();
  const params = new URLSearchParams(location.search);
  const asNo = params.get("as_no");
  const { audioURL, setAudioURL, setAudioExtension } = useContext(MainContext);
  const [status, setStatus] = useState("");
  const [audioSrc, setAudioSrc] = useState("");
  const [audioType, setAudioType] = useState("audio/mp3");
  const [shouldHide, setShouldHide] = useState(false);
  const audioContextRef = useRef(null);
  const recorderDestinationRef = useRef(null);

  // Web Audio API 설정
  useEffect(() => {
    const initializeAudioContext = async () => {
      audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)();
      recorderDestinationRef.current = audioContextRef.current.createMediaStreamDestination();

      // 마이크 스트림을 미리 활성화
      try {
        const stream = await navigator.mediaDevices.getUserMedia({
          audio: {
            echoCancellation: true,      // 에코 제거
            noiseSuppression: true,      // 노이즈 억제
            autoGainControl: false       // 자동 이득 제어(AGC) 비활성화
          }
        });

        const microphone = audioContextRef.current.createMediaStreamSource(stream);

        // 마이크 볼륨 조절용 GainNode
        const micGain = audioContextRef.current.createGain();
        micGain.gain.value = 1.5; // 마이크 입력 볼륨 증폭

        // Notch 필터 적용 (특정 주파수 대역 제거)
        const notchFilter = audioContextRef.current.createBiquadFilter();
        notchFilter.type = 'notch';
        notchFilter.frequency.value = 440; // 필요에 따라 음원 주파수에 맞춰 설정
        notchFilter.Q.value = 10; // 필터 대역폭 설정

        // Lowpass 필터 추가 (고주파수 제거)
        const lowpassFilter = audioContextRef.current.createBiquadFilter();
        lowpassFilter.type = 'lowpass';
        lowpassFilter.frequency.value = 1000; // 필요에 따라 조정

        // Compressor 추가 (다이내믹 레인지 압축)
        const compressor = audioContextRef.current.createDynamicsCompressor();
        compressor.threshold.setValueAtTime(-50, audioContextRef.current.currentTime); // 클리핑 방지
        compressor.knee.setValueAtTime(40, audioContextRef.current.currentTime); // 부드러운 압축
        compressor.ratio.setValueAtTime(12, audioContextRef.current.currentTime); // 압축 비율
        compressor.attack.setValueAtTime(0, audioContextRef.current.currentTime); // 빠른 반응 시간
        compressor.release.setValueAtTime(0.25, audioContextRef.current.currentTime); // 빠른 해제 시간

        // 마이크 -> Notch 필터 -> Lowpass 필터 -> Compressor -> Gain -> 녹음 스트림
        microphone.connect(notchFilter);
        notchFilter.connect(lowpassFilter);
        lowpassFilter.connect(compressor);
        compressor.connect(micGain);
        micGain.connect(recorderDestinationRef.current); // 녹음 스트림으로 연결
      } catch (error) {
        console.error('마이크 입력을 가져오는 데 실패했습니다:', error);
      }
    };

    initializeAudioContext();
  }, []);

  const Mp = EffectSound(MP, 1);

  const controlAudio = (status) => {
    setStatus(status);
  };

  const toggleRecording = () => {
    status === "recording"
      ? controlAudio("inactive")
      : controlAudio("recording");
  };

  const audioProps = {
    audioType,
    status,
    audioSrc,
    timeslice: 1000,
    startCallback: (e) => {
      console.log("succ start", e);
    },
    pauseCallback: (e) => {
      console.log("succ pause", e);
    },
    stopCallback: (e) => {
      setAudioSrc(window.URL.createObjectURL(e));
      console.log("succ stop", e);
      setAudioURL(window.URL.createObjectURL(e));

      const formData = new FormData();
      formData.append("assignment", asNo);
      formData.append("mp3", e);
      Axios.put(`${API_URL}api/stt`, formData, {
        headers: {
          "Content-Type": "multipart/form-data",
        },
        withCredentials: true,
      })
        .then((response) => {
          message.success("임시저장 완료");
          props.setSubmitlist(response.data.file);
          props.setTemporarySubmitCheck(true);
        })
        .catch((error) => {
          console.error("파일 업로드 실패:", error);
        });
    },
    onRecordCallback: (e) => {
      console.log("recording", e);
    },
    errorCallback: (err) => {
      console.log("error", err);
    },
    // recorderDestinationRef.current가 null이 아닌지 확인
    mediaStream: recorderDestinationRef.current ? recorderDestinationRef.current.stream : null 
  };

  const onRecordCheck = () => {
    if (recorderDestinationRef.current) {
      toggleRecording();
      setShouldHide(false);
      props.setLoading(true);
    } else {
      console.error("Recorder is not ready yet.");
    }
  };

  useEffect(() => {
    setAudioExtension(audioType.replace("audio/", ""));
  }, [setAudioExtension, audioType]);

  useEffect(() => {
    if (props.Startmusic && props.Disable === props.region_index) {
      if (recorderDestinationRef.current) {
        toggleRecording();
        props.setStartmusic(false);
        setShouldHide(true);
        console.log("녹음 시작");
        Mp.play();
      } else {
        console.error("Recorder is not ready yet.");
      }
    }
  }, [props.Startmusic]);

  return (
    <div style={{ margin: "10px" }}>
      <AudioAnalyser {...audioProps} width={290}>
        {shouldHide && (
          <div className="btn-box">
            <RecordButton id="recordButton" onClick={onRecordCheck} />
          </div>
        )}
      </AudioAnalyser>
    </div>
  );
}

 

 

 

고찰


아무래도 가장 큰 요인은 "마이크 스트림과 오디오 재생 간의 상호작용 문제" , "오디오 피드백 방지를 위한 오디오 이득 제어(AGC), 노이즈 억제 문제" 라고 생각합니다.

 

우선 위 두개 문제점의 공통점은 특정 설정이 없을 시, 초기값은 모두 브라우저의 제어권으로 통제된다는 점입니다.

위 오디오 설정을 통해 Web Audio API의 마이크 설정을 해줌으로써 음원, 녹음 동시 동작이라는 특정 상황인 문제를 해결했다고 생각합니다.

가장 어려웠던 점은 음원의 잡음 주파수를 잡아내는 것과 고주파 잡음을 제거하여, 지나치게 높은 주파수의 소음이 녹음에 섞이는 것을 방지하는 것이였습니다. 잡음 주파수를 어느정도 hz로 설정한다는 자료가 국내에 없어 해외 자료를 찾던 중 440을 기본으로 설정한다고 하여 설정을 진행했습니다.

 

하지만 잡음이 계속 진행될 경우, 따로 측정하여 분석 후, 실제 제거하려는 주파수에 따라 조정해야한다고 합니다.3

 

또한, 보통 인간의 음성 주파수를 조사하여 85Hz에서 255Hz 정도이므로, 1,000Hz 이상을 제거함으로써 음성에 영향을 주지 않으면서 불필요한 고주파 잡음을 차단하는 과정은 개발의 범주를 넘어 음악까지 도메인을 넓혀야 해결이 가능했기에 너어어어어무 힘들었습니다.....