녹음은 두 가지 서로 다른 방식으로 나빠질 수 있으며, 그 해법은 겹치지 않습니다.
첫 번째 방식: 배경 잡음이 음성 위에 얹힙니다 — 교통 소음, 선풍기, 방의 웅웅거림. 중요한 신호는 거기 있지만, 다른 무언가가 그 안에 섞여 있습니다. 이를 제거하는 것이 잡음 제거입니다.
두 번째 방식: 녹음이 애초에 전체 신호를 담지 못했습니다. 전화 음성은 초당 8,000 표본(8kHz)으로 표본화됩니다. 완전한 품질의 녹음은 대개 48kHz입니다. 표본율은 디지털 신호가 나타낼 수 있는 최고 주파수를 결정하므로, 8kHz 통화에는 4kHz 이상의 내용이 전혀 없습니다 — 조용한 게 아니라 필터링된 것도 아니라, 그저 한 번도 녹음된 적이 없는 것입니다. 그 오디오를 다시 온전하게 들리게 만든다는 것은 결코 포착된 적 없는 그럴듯한 고주파를 지어낸다는 뜻입니다. 그것이 대역폭 확장입니다.
audiosronnx는 이 둘을 서로 다른 진입점을 가진 두 개의 다른 문제로 취급합니다. 잘못된 것을 사용하면 잘못된 일을 하기 때문입니다. 대역폭 확장기를 잡음이 있는 신호에 돌리면 그 잡음의 고주파 버전을 충실하게 재구성해 버립니다. 잡음 제거가 먼저 일어나야 합니다.
from audiosronnx import load_denoise, load_sr
clean, rate = load_denoise("dpdfnet").denoise("noisy_call.wav") # remove noise
wide, _ = load_sr("lavasr").upscale(clean, rate) # extend to 48 kHz
load_denoise()와 load_sr()는 서로의 엔진을 거부합니다 — load_denoise에게 대역폭 확장기를 요청하면 조용히 잘못된 일을 하는 대신 오류를 냅니다.
왜 이것이 인식보다 먼저인가
음성 인식과 화자 식별은 대개 비교적 깨끗한 오디오로 학습됩니다. 인식기에 8kHz 전화 음성이나 선풍기가 돌아가는 음성을 넣으면 단어 오류율이 올라갑니다 — 모델이 나빠서가 아니라, 입력이 더 이상 학습 당시 보았던 것처럼 보이지 않기 때문입니다. 식별이나 화자 분리에 사용되는 화자 임베딩도 마찬가지입니다: 잡음과 없어진 대역폭은 그 임베딩이 의존하는 정확한 음향 세부 사항을 왜곡합니다.
이는 정리 작업을 인식의 대안이 아니라 인식 이전에 있는 파이프라인 단계로 만듭니다. 시끄러운 8kHz 전화 통화를 위한 실제 파이프라인은 이렇습니다: 잡음을 제거하고, 48kHz로 확장한 다음, 그 결과에 인식이나 화자 식별을 실행합니다. 입력을 먼저 고치지 않고 더 나은 인식기를 끼워 넣는 것은 엉뚱한 곳에 노력을 쓰는 것입니다 — 모델이 아무리 좋아도 같은 손상된 신호에서는 성능이 떨어집니다.
엔진 레지스트리
audiosronnx는 열 개의 잡음 제거기와 일곱 개의 대역폭 확장기를 제공하며, 모두 load_denoise() / load_sr()을 통해 이름으로 로드할 수 있고, 모두 런타임에 torch 없이 순수 ONNX입니다.
잡음 제거기:
| 엔진 | 표본율 | 크기 | 라이선스 |
|---|---|---|---|
| dpdfnet (기본값) | 8/16/48 kHz | 8.7–14.9 MB | Apache-2.0 |
| mossformer2 | 48 kHz | 229 MB | Apache-2.0 |
| frcrn | 16 kHz | 57.5 MB | Apache-2.0 |
| mpsenet | 16 kHz | 9.7 MB | MIT |
| gtcrn | 16 kHz | 0.54 MB | MIT |
| cmgan | 16 kHz | 7.8 MB | MIT |
| metadenoiser | 16 kHz | 19–34 MB | CC-BY-NC-4.0 |
| mossformergan | 16 kHz | 17.7 MB | Apache-2.0 |
| voicefixer | 44.1 kHz | 415 MB | MIT |
| deepfilternet | 48 kHz | ~2 MB | MIT |
대역폭 확장기:
| 엔진 | 입력 | 크기 | 라이선스 |
|---|---|---|---|
| lavasr (기본값) | 8–48 kHz | ~52 MB | Apache-2.0 |
| novasr | 16 kHz | ~0.2 MB | Apache-2.0 |
| flowhigh | 임의 | ~200 MB | MIT |
| hifiganbwe | 임의 | ~4 MB | MIT |
| apbwe | 임의(12kHz 대역) | ~120 MB | MIT |
| sidon | 16 kHz | ~410 MB | MIT |
| callenhancer | 8–16 kHz | ~3 GB / int8 ~1.3 GB | CC-BY-NC-4.0 |
라이브러리에서 가장 작은 모델인 gtcrn은 0.54MB입니다. 가장 큰 voicefixer는 415MB로 — 거의 800배 더 크며, 다른 일을 합니다: 이는 잡음, 잔향, 클리핑, 없어진 대역폭을 한 번에 하나씩이 아니라 함께 처리하는 복원 모델입니다.
대부분의 가중치는 MIT나 Apache-2.0입니다. 두 가지는 그렇지 않습니다: metadenoiser와 callenhancer는 비상업용인 CC-BY-NC-4.0으로 제공됩니다. 그 라이선스는 가중치를 다루는 것이지 그것으로 처리된 오디오를 다루는 것이 아니며, 라이브러리는 사용되는 모든 지점에서 이를 명시합니다 — audiosronnx list는 엔진별로 이를 보고합니다. 호출자가 시간 영역 아키텍처 때문에 metadenoiser를 선택하는 것을 막지는 않지만, 그 선택은 알고 이루어져야 합니다.
레지스트리가 존재하는 이유는 모든 녹음에서 이기는 단일 모델이 없기 때문입니다. dpdfnet이 기본값인 이유는 추가 의존성이 필요 없고 하나의 모델로 8, 16, 48kHz를 모두 다루기 때문입니다. mossformer2는 풀밴드 입력에서 측정된 최고의 선택입니다. mossformergan은 제공되는 잡음 제거기 중 가장 높은 공개 PESQ 점수(3.47)를 기록합니다. gtcrn은 제약 조건이 용량일 때의 선택이며, 0.54MB입니다. 광대역 가우시안 잡음이 있는 테스트 클립 하나에서, 잡음 제거기들은 입력 SNR 19dB에서 3.55.9dB의 SNR을 회복했고, 더 어려운 입력 SNR 5dB에서는 7.513.7dB까지 올랐습니다. 이는 합성된, 가혹한 잡음 사례입니다: 엔진들의 순위는 일관되게 매기지만 잡담 잡음이나 코덱 아티팩트에 대해서는 거의 말해주지 않으며, 이것이 바로 레지스트리가 승자 하나만 배포하지 않고 열 개의 모델을 유지하는 이유입니다.
cmgan은 의도적으로 유지되는, 밀리는데도 남아 있는 가장 분명한 사례입니다: PESQ와 SNR 양쪽에서 열네 배 작은 gtcrn에게 밀리는데도 그대로 남아 있습니다 — cmgan을 대상으로 구축된 공개 결과가 재현 가능한 상태를 유지하고, 비교할 별개의 아키텍처가 남아 있도록 하기 위해서입니다.
대역폭 확장 쪽에서, sidon과 callenhancer는 lavasr나 novasr와는 다른 일을 합니다: 기존 신호 위에 그럴듯한 고음역대를 더하는 대신, 훨씬 높은 연산 비용으로 신경 보코더를 통해 음성을 처음부터 재합성하며, 이는 대역 확장기가 손댈 수 없는 코덱 손상을 복구할 수 있습니다. callenhancer는 통신 음성에 특화되어 학습되었으며, 이것이 그 가중치가 비상업용 라이선스를 지니는 이유입니다.
포함되지 못한 것들
audiosronnx는 하나의 정적 ONNX 그래프로 내보내지고, onnxruntime을 통해 CPU에서 실행되며, 명확한 라이선스를 지니고, 원본 구현에 대해 처음부터 끝까지 검증된 경우에만 엔진을 제공합니다 — 원본 모델뿐 아니라 그 전체 과정에 대해서입니다. 네트워크와는 일치하지만 그 주변 정규화와는 일치하지 않는 그래프는 그럴듯하게 들리면서 조용히 틀린 오디오를 만들어내기 때문입니다.
이 프로젝트의 docs/not-shipped.md는 평가했지만 반려한 모든 후보를 구체적인 이유와 함께 문서화합니다. 이는 이 저장소에서 가장 유용한 문서 중 하나인데, “순수 ONNX, CPU 전용”이 오늘날 무엇을 할 수 있는지의 실제 경계를 그저 단언하는 대신 보여주기 때문입니다.
반복 샘플러에는 내보낼 정적 그래프가 없습니다. 확산 및 흐름 매칭 모델은 발화당 네트워크를 여러 번 실행하며, 그 루프의 길이는 내보내는 시점에 고정되어 있지 않습니다. AudioSR(별도의 VAE, LDM, 보코더를 가진 대략 6GB짜리 잠재 확산 파이프라인)과 SGMSE 둘 다 여기에 해당합니다 — SGMSE 자체의 2025년 스트리밍 후속작도 소비자용 GPU에서야 겨우 실시간에 도달하며, CPU는 말할 것도 없습니다.
위치 가변 합성곱은 실격처럼 보이지만 대체로 그렇지 않습니다. resemble-enhance는 오랫동안 이 문서에서 보코더의 위치 가변 합성곱인 LVCNet 때문에 반려되었습니다. unfold와 einsum을 통해 위치별로 예측되는 커널이 정적 그래프로 접혀 들어갈 수 없다는 이론 때문이었습니다. 직접 테스트해 보니 이는 틀린 것으로 드러났습니다 — 두 연산 모두 ONNX 대응물이 있습니다. 실제 실패 원인은 별개의, 잘 알려진 트레이싱 오류(“알 수 없는 모양의 커널에 대한 합성곱의 ONNX 내보내기”)로, BigVGAN의 리샘플러에 대해 코드베이스 다른 곳에서 이미 해결된 문제입니다. resemble-enhance를 여전히 배제하는 것은 규모입니다: CFM ODE 샘플러와 오토인코더를 포함한 네 개의 네트워크가 44.1kHz로 — 불가능이 아니라 범위에 대한 결정입니다.
일부 모델은 내보낼 학습된 것이 전혀 없습니다. RNNoise는 학습 가능한 프레임워크의 그래프가 아니라 손으로 작성한 C로 제공됩니다 — 이를 이식한다는 것은 처음부터 동등한 네트워크를 재학습시킨다는 뜻입니다. Fast-ULCNet은 아키텍처 코드만 공개하며, 체크포인트는 전혀 없습니다.
제한적인 라이선스는 자동 실격이 아니라 표시 결정입니다 — 이것이 정확히 callenhancer와 metadenoiser가 제공되는 이유입니다. 실격시키는 것은 라이선스가 전혀 없이 공개된 가중치입니다: mdctGAN은 신뢰성 있게 내보내지지 않는 torch.fft 기반 프론트엔드에 더해 정확히 그 이유로 반려되었습니다.
모델 내부에서 호출되는 torch.stft는 실제 구조적 걸림돌입니다. NU-Wave2의 샘플러 루프는 문제가 아닙니다 — 다른 모든 엔진의 STFT처럼 그래프 밖의 numpy에서 실행될 수 있습니다. 문제는 그 forward 메서드가 내부적으로 torch.stft와 torch.istft를 호출한다는 것이며, 라이브러리는 이를 제공하는 모든 그래프에서 의도적으로 배제하는데, 이는 또한 일반적으로 가장 신뢰성 있게 내보내지지 않는 연산자이기도 합니다. 이를 고치려면 변환 경계에서 모델을 나누는, 연산자 교체가 아닌 실제 재구조화가 필요합니다.
모델의 아키텍처를 재현하는 것이 그 출력을 재현하는 것과 같지는 않습니다. LiSenNet은 5만 6천 개의 파라미터, 300KB 미만으로 — 이 라이브러리에서 가장 작은 엔진이 될 것입니다. 공개된 ONNX 포트는 실행되고 그럴듯해 보이는 감쇠된 오디오를 만들어내지만, 처음부터 끝까지 측정하면 신호를 파괴합니다: 입력 11dB에서 SNR −10.8dB. 그 포트 자체의 참조 구현을 정확히 재현해도 동일한 음수 결과가 나오는데, 이는 참조 구현 자체가 자신의 문서가 기술하는 프론트엔드와 일치하지 않는다는 뜻입니다 — 아직 검증할 올바른 목표가 없는 것입니다.
이 모든 것을 관통하는 패턴은, 흥미로운 실패가 “모델이 너무 크다”거나 “확산이 느리다” 같은 것이 아니라는 것입니다. 그것들은 구체적입니다: 정확한 대체물이 있는 지원되지 않는 연산자(torch.complex는 ONNX 연산이 없지만, atan2(im, re)가 같은 위상각을 계산합니다), 입력의 런타임 모양으로부터 만들어져 트레이서가 고정할 수 없는 텐서, 혹은 그래프 경계의 잘못된 쪽에 놓인 변환 같은 것들입니다.
출력이 실제로 나아졌는지 확인하기
실행되는 ONNX 파일이 녹음이 개선되었다는 증명은 아닙니다. 서로 다른 두 실패 모드가 겉보기엔 똑같아 보입니다: 잡음과 함께 음성도 죽이는 잡음 제거기와, 잘못된 배음 내용을 가진 고음역대를 더하는 대역폭 확장기 둘 다 오류 없이 재생되며 가볍게 들었을 때는 오히려 더 깨끗하게 들릴 수도 있습니다.
자매 라이브러리인 speechonnxmetrics는 그 판단을 인상이 아니라 측정 가능하게 만들기 위해 존재합니다. 오디오를 MOS(평균 의견 점수, 지각된 품질에 대한 1–5 평가)로 두 가지 방식으로 채점합니다: 비교할 깨끗한 원본 없이 녹음을 채점하는 DNSMOS나 UTMOS 같은 참조 없는 신경 예측기, 그리고 깨끗한 참조가 필요하며 출력이 실제로 그것에 얼마나 가까운지 측정하는 STOI나 SI-SDR 같은 침습적 지표입니다.
import speechonnxmetrics as s
s.score("clean.wav", ["utmos"])
# -> {'utmos': 4.41}
s.score("denoised.wav", ["stoi", "si_sdr"], ref="clean.wav")
# -> {'stoi': 0.66, 'si_sdr': -26.9}
잡음 제거기나 확장기의 전과 후를 실행하면 실제 비교의 형태가 드러납니다: 원본과 처리된 오디오에 DNSMOS나 UTMOS를 돌려 지각 품질이 조금이라도 움직였는지 보고, 깨끗한 참조가 있을 때(합성 잡음 테스트에서는 있지만 실제 전화 통화에서는 거의 없습니다) SI-SDR이나 STOI로 처리된 신호가 단순히 다르게 들리는 게 아니라 실제로 그것에 수렴했는지 봅니다. 이는 위 잡음 제거기 표의 SNR 수치 뒤에 있는 것과 같은 규율입니다: 형용사가 아니라 특정 잡음 조건에 붙은 숫자입니다. speechonnxmetrics 자체를 포함해 이것이 속한 더 넓은 순수 ONNX 라이브러리 계열은 순수 ONNX 음성 라이브러리 제품군에서 다룹니다.
오디오가 인식기, 화자 식별 시스템, 또는 사람 청취자에게 도달하기 전에 정리하는 것은 그 자체로 하나의 엔지니어링 작업이며, 자체적인 트레이드오프 목록과 시도했지만 실제 신호와의 접촉에서 살아남지 못한 접근법들의 목록을 가지고 있습니다.
특정 파이프라인에 이를 적용하는 것에 대한 질문은 문의해 주세요.