dBA와 A-Weighting

이영섭·2025년 9월 7일

소음 측정 프로젝트

목록 보기
10/12

서론

지난 글에서는 decibel을 구현하는 것까지 진행하였다면 이번에는 decibel을 dBA로 전환할 것이다. dB(decibel)과 dBA의 차이는 무엇일까?
dB(데시벨)은 소리의 크기(음압)를 나타내는 단위이며, dBA는 dB 단위에 인간의 청감 특성을 고려한 'A-가중치'를 적용한 것이다.
쉽게 풀어 말하자면, 측정된 각 주파수에 인간의 귀가 주파수별로 민감도가 다르다는 특성을 반영한 A-가중치(A-weighting)을 부여한다.

A-Weighting을 꼭 적용해야 할까?
프로젝트가 소음 측정이라면 반드시 적용해야 한다.
예를 들어 250Hz 주파수의 보정값은 -8.6dB의 보정이 필요한데
이를 적용하지 않는다면 사람이 실제로 느끼는 소리보다 약 2.7배 더 크게 측정되는 오류가 발생할 수 있다.

library로 적용하면 되지 않을까?
안타깝게도 JS로 구성된 A-Weighting library는 발견할 수 없었다.
github 검색시 A-weighting library가 존재하긴 하나 A-가중치 뿐만 아니라 B,C,D 가중치가 존재하기도 했고, 공식과 다르다는 느낌이 들어 직접 구현하는 것이 정확할 것이라는 판단이 들었다.

	const startButton = document.getElementById('startButton');
    const dbDisplay = document.getElementById('current-db');
    let audioContext = null;
    let analyser = null;
    let dataArray = null;

    startButton.addEventListener('click', (event) => {
        event.preventDefault();
        if (!audioContext) {
            startMeasurement();
        }
    });

    function startMeasurement() {
        audioContext = new (window.AudioContext || window.webkitAudioContext)();
        
        navigator.mediaDevices.getUserMedia({ audio: true })
            .then(stream => {
                const source = audioContext.createMediaStreamSource(stream);
                analyser = audioContext.createAnalyser();
                analyser.fftSize = 2048;
                analyser.minDecibels = -100; // AnalyserNode의 기본 dB 범위로 설정
                analyser.maxDecibels = -30;
                dataArray = new Uint8Array(analyser.frequencyBinCount);
                source.connect(analyser);

                console.log("A-weighting 소음 측정을 시작합니다.");
                updateDb();
            })
            .catch(err => {
                console.error("마이크 접근에 실패했습니다:", err);
            });
    }

    // A-weighting 계수 계산 함수
    function getAWeightingForFrequency(f) {
        const f2 = f * f;
        const f4 = f2 * f2;
        const a1 = 12200 * 12200 * f4;
        const a2 = (f2 + 20.6 * 20.6) * Math.sqrt((f2 + 107.7 * 107.7) * (f2 + 737.9 * 737.9));
        const a3 = f2 + 12200 * 12200;
        const r_a = a1 / (a2 * a3);
        const a_db = 20 * Math.log10(r_a) + 2.0;
        return a_db;
    }

    function updateDb() {
        analyser.getByteFrequencyData(dataArray);

        const bufferLength = analyser.frequencyBinCount;
        const sampleRate = audioContext.sampleRate;
        
        let sumOfSquares = 0;
        
        for (let i = 0; i < bufferLength; i++) {
            const frequency = i * sampleRate / bufferLength;
            const aWeightingDb = getAWeightingForFrequency(frequency);
            const amplitude = dataArray[i];
            
            // amplitude(0-255)를 analyser의 dB 범위로 변환
            const amplitudeDb = (amplitude / 255) * (analyser.maxDecibels - analyser.minDecibels) + analyser.minDecibels;
            
            // RMS 계산을 위해 선형 값으로 변환하고 A-weighting 보정값을 더함
            const weightedAmplitudeDb = amplitudeDb + aWeightingDb;
            const weightedAmplitudeLinear = Math.pow(10, weightedAmplitudeDb / 20);

            sumOfSquares += weightedAmplitudeLinear * weightedAmplitudeLinear;
        }

        const rms = Math.sqrt(sumOfSquares / bufferLength);
        let dbA = rms > 0 ? 20 * Math.log10(rms) : -100; // -100으로 설정하여 음수 값이 나오게 함
        
        // 최종 캘리브레이션 적용
        const calibrationOffset = 110; // 교정을 통해 얻은 상수를 사용하세요.
        dbA = dbA + calibrationOffset;

        // 최종 값이 120dB을 초과하지 않도록 제한
        if (dbA > 120) {
            dbA = 120;
        }
        
        // 최종 값이 0dB 미만일 경우 0으로 설정
        if (dbA < 0) {
            dbA = 0;
        }
        
        dbDisplay.textContent = dbA.toFixed(2);
        requestAnimationFrame(updateDb);
    }

위 코드에서 구현된 가중치는 국제 표준(IEC 61672)에 정의된 A-weighting 필터의 동일한 수학 공식을 기반으로 한다.

각 A-가중치 적용 절차는 다음과 같다.

1. 주파수 대역 계산

for 루프에서 frequency = i * sampleRate / bufferLength; 코드를 통해 AnalyserNode의 각 데이터 인덱스가 어떤 주파수 대역을 나타내는지 정확하게 계산한다.

2. 보정값 적용

getAWeightingForFrequency(frequency) 함수를 호출하여 각 주파수에 맞는 A-weighting 보정값(aWeightingDb)을 얻는다.

3. 가중치 부여

amplitudeDb + aWeightingDb 이 부분에서 AnalyserNode가 측정한 원본 주파수별 음량(amplitudeDb)에 계산된 보정값을 더한다.

4. 최종 dBA 계산

보정된 값들을 선형 값으로 다시 변환한 후, RMS 계산과 로그 변환을 통해 최종적인 dBA 값을 얻는다.

다만, 여기서 calibrationOffset이라는 교정값을 더했는데 그 이유는 본 프로젝트는 0~120dB로 사람에게 체감되는 수치로 나누었는데, 마이크의 상태와 오디오 환경에 따라 음의 dBA 수치가 나올 수 있어, 110이라는 임의의 값을 집어넣은 상태이다. 좀 더 교정을 위해서는 소음 측정계 혹은 핸드폰의 소음 측정기 어플들을 사용해 값을 측정하여 차이를 활용해 교정해 나가야 한다.

profile
신입 개발자 지망생

0개의 댓글