Skip to content
Published on

torchaudio 완전 가이드 — 오디오 처리부터 음성인식, TTS, 음악 분석까지

Authors
  • Name
    Twitter
torchaudio Guide

들어가며

torchaudio는 PyTorch의 공식 오디오 처리 라이브러리입니다. 오디오 I/O, 스펙트로그램 변환, 사전학습 모델(Wav2Vec2, HuBERT, Whisper), 그리고 실시간 스트리밍까지 지원합니다.

pip install torch torchaudio

Part 1: 오디오 기초

오디오 로드 및 저장

import torch
import torchaudio

# 오디오 로드
waveform, sample_rate = torchaudio.load("speech.wav")
print(f"Shape: {waveform.shape}")    # [channels, samples]
print(f"Sample Rate: {sample_rate}")  # 16000
print(f"Duration: {waveform.shape[1] / sample_rate:.2f}s")

# 채널: 모노(1) vs 스테레오(2)
if waveform.shape[0] == 2:
    mono = waveform.mean(dim=0, keepdim=True)  # 스테레오 → 모노

# 리샘플링 (44100Hz → 16000Hz)
resampler = torchaudio.transforms.Resample(
    orig_freq=44100, new_freq=16000
)
waveform_16k = resampler(waveform)

# 저장
torchaudio.save("output.wav", waveform_16k, 16000)

# 지원 포맷: wav, flac, mp3, ogg, opus, sphere
# 백엔드: sox, soundfile, ffmpeg
print(torchaudio.list_audio_backends())

오디오 시각화

import matplotlib.pyplot as plt

# 파형 (Waveform)
fig, axes = plt.subplots(3, 1, figsize=(12, 8))

# 1. 시간 도메인 (파형)
time_axis = torch.arange(0, waveform.shape[1]) / sample_rate
axes[0].plot(time_axis, waveform[0])
axes[0].set_title("Waveform")
axes[0].set_xlabel("Time (s)")
axes[0].set_ylabel("Amplitude")

# 2. 스펙트로그램
spectrogram = torchaudio.transforms.Spectrogram(n_fft=1024)(waveform)
axes[1].imshow(
    spectrogram[0].log2().numpy(),
    aspect='auto', origin='lower', cmap='magma'
)
axes[1].set_title("Spectrogram")

# 3. Mel 스펙트로그램
mel_spec = torchaudio.transforms.MelSpectrogram(
    sample_rate=sample_rate, n_fft=1024, n_mels=80
)(waveform)
axes[2].imshow(
    mel_spec[0].log2().numpy(),
    aspect='auto', origin='lower', cmap='magma'
)
axes[2].set_title("Mel Spectrogram")

plt.tight_layout()
plt.savefig("audio_analysis.png", dpi=150)

Part 2: 핵심 변환 (Transforms)

스펙트로그램 계열

# STFT (Short-Time Fourier Transform)
# 시간 → 시간+주파수 영역으로 변환
spectrogram_transform = torchaudio.transforms.Spectrogram(
    n_fft=1024,       # FFT 윈도우 크기 (주파수 해상도)
    hop_length=256,    # 윈도우 이동 간격 (시간 해상도)
    win_length=1024,   # 윈도우 길이
    power=2.0,         # 2.0=파워, 1.0=진폭
)

spec = spectrogram_transform(waveform)
# shape: [channels, n_freq_bins, time_frames]
# n_freq_bins = n_fft // 2 + 1 = 513
n_fft와 hop_length의 트레이드오프:

n_fft ↑ → 주파수 해상도 ↑, 시간 해상도 ↓
n_fft ↓ → 주파수 해상도 ↓, 시간 해상도 ↑

일반적인 설정:
├── 음성: n_fft=400~512, hop=160 (16kHz 기준)
├── 음악: n_fft=2048, hop=512 (44.1kHz 기준)
└── 범용: n_fft=1024, hop=256

Mel 스펙트로그램 — 왜 Mel인가?

# 사람의 귀는 저주파에 민감, 고주파에 둔감
# Mel 스케일 = 인간 청각을 반영한 주파수 스케일

mel_transform = torchaudio.transforms.MelSpectrogram(
    sample_rate=16000,
    n_fft=1024,
    hop_length=256,
    n_mels=80,          # Mel 필터 개수 (보통 40~128)
    f_min=0,            # 최소 주파수
    f_max=8000,         # 최대 주파수 (Nyquist)
)

mel_spec = mel_transform(waveform)
# shape: [1, 80, time_frames]

# dB 스케일 변환 (로그 압축)
amplitude_to_db = torchaudio.transforms.AmplitudeToDB(stype='power', top_db=80)
mel_spec_db = amplitude_to_db(mel_spec)
Mel 주파수 변환 공식:
  mel = 2595 × log10(1 + freq / 700)

주파수 → Mel:
  100 Hz150 mel   (저주파: 조밀)
  1000 Hz1000 mel
  4000 Hz2146 mel
  8000 Hz2840 mel  (고주파: 성긴)

→ 저주파는 세밀하게, 고주파는 뭉뚱그려서 분석
→ 사람이 듣는 것과 비슷한 표현!

MFCC (Mel-Frequency Cepstral Coefficients)

# MFCC = Mel 스펙트로그램 + DCT (이산 코사인 변환)
# 음성의 "형태"를 나타내는 핵심 특성

mfcc_transform = torchaudio.transforms.MFCC(
    sample_rate=16000,
    n_mfcc=13,          # MFCC 계수 개수 (보통 13~40)
    melkwargs={
        'n_fft': 1024,
        'n_mels': 80,
        'hop_length': 256,
    }
)

mfcc = mfcc_transform(waveform)
# shape: [1, 13, time_frames]

# Delta (1차 미분) + Delta-Delta (2차 미분)
# → 음성의 변화율 정보 추가
delta = torchaudio.functional.compute_deltas(mfcc)
delta_delta = torchaudio.functional.compute_deltas(delta)

# 최종 특성: [MFCC, Delta, Delta-Delta] 연결
features = torch.cat([mfcc, delta, delta_delta], dim=1)
# shape: [1, 39, time_frames]
어디에 쓰이나?
├── MFCC: 전통 음성인식 (HMM-GMM), 화자 인식
├── Mel Spectrogram: 딥러닝 음성인식 (Wav2Vec2, Whisper)
├── Spectrogram: 음악 분석, 환경음 분류
└── Raw Waveform: End-to-end 모델 (최신 트렌드)

Part 3: 오디오 Augmentation

# 시간 마스킹 (SpecAugment)
time_masking = torchaudio.transforms.TimeMasking(
    time_mask_param=30   # 최대 30 프레임 마스킹
)

# 주파수 마스킹 (SpecAugment)
freq_masking = torchaudio.transforms.FrequencyMasking(
    freq_mask_param=15   # 최대 15 채널 마스킹
)

# SpecAugment (음성인식 정확도 크게 향상!)
augmented_spec = time_masking(freq_masking(mel_spec))

# 시간 늘리기/줄이기
time_stretch = torchaudio.transforms.TimeStretch()
stretched = time_stretch(complex_spec, overriding_rate=1.2)  # 20% 빠르게

# 피치 변환
pitch_shift = torchaudio.transforms.PitchShift(
    sample_rate=16000, n_steps=4  # 4 반음 올리기
)
shifted = pitch_shift(waveform)

# 노이즈 추가
def add_noise(waveform, snr_db=10):
    """SNR dB 기준으로 백색 노이즈 추가"""
    noise = torch.randn_like(waveform)
    signal_power = waveform.norm(p=2)
    noise_power = noise.norm(p=2)
    snr = 10 ** (snr_db / 20)
    scale = signal_power / (snr * noise_power)
    return waveform + scale * noise

Part 4: 사전학습 모델

Wav2Vec 2.0 (음성인식)

import torchaudio
from torchaudio.pipelines import WAV2VEC2_ASR_BASE_960H

# 파이프라인 로드
bundle = WAV2VEC2_ASR_BASE_960H
model = bundle.get_model()
labels = bundle.get_labels()  # 토큰 목록

# 추론
waveform, sr = torchaudio.load("speech.wav")
if sr != bundle.sample_rate:
    waveform = torchaudio.transforms.Resample(sr, bundle.sample_rate)(waveform)

with torch.no_grad():
    emissions, _ = model(waveform)

# CTC 디코딩 (Greedy)
def greedy_decode(emissions, labels):
    indices = torch.argmax(emissions, dim=-1)[0]
    tokens = []
    prev = -1
    for idx in indices:
        if idx != prev and idx != 0:  # 0 = blank
            tokens.append(labels[idx])
        prev = idx
    return "".join(tokens).replace("|", " ").strip()

text = greedy_decode(emissions, labels)
print(f"인식 결과: {text}")

HuBERT (자기지도 음성 표현)

from torchaudio.pipelines import HUBERT_BASE

bundle = HUBERT_BASE
model = bundle.get_model()

with torch.no_grad():
    features, _ = model(waveform)
# features: [1, time_frames, 768]
# → 음성의 의미적 표현 벡터
# → 화자 인식, 감정 분석, 음성 분류에 활용

Forced Alignment (자막 동기화)

# 음성과 텍스트의 시간 정렬!
# → 자막 생성, 가사 동기화에 필수

from torchaudio.pipelines import MMS_FA  # Multilingual!

bundle = MMS_FA
model = bundle.get_model()
tokenizer = bundle.get_tokenizer()
aligner = bundle.get_aligner()

transcript = "안녕하세요 반갑습니다"
tokens = tokenizer(transcript)

with torch.no_grad():
    emissions, _ = model(waveform)

token_spans = aligner(emissions[0], tokens)
# 각 토큰의 시작/끝 시간을 프레임 단위로 반환!

for span, token in zip(token_spans, transcript):
    start_time = span.start * model.hop_length / sample_rate
    end_time = span.end * model.hop_length / sample_rate
    print(f"  '{token}': {start_time:.3f}s ~ {end_time:.3f}s")

Part 5: 오디오 이펙트

# torchaudio.functional — GPU 가속 오디오 처리

import torchaudio.functional as F

# 볼륨 조절
loud = F.gain(waveform, gain_db=6.0)    # +6dB
quiet = F.gain(waveform, gain_db=-6.0)  # -6dB

# 하이패스/로우패스 필터
highpass = F.highpass_biquad(waveform, sample_rate, cutoff_freq=300)
lowpass = F.lowpass_biquad(waveform, sample_rate, cutoff_freq=3000)

# 이퀄라이저
eq = F.equalizer_biquad(
    waveform, sample_rate,
    center_freq=1000,  # 1kHz 부근
    gain=5.0,          # +5dB 부스트
    Q=0.707
)

# 리버브 (잔향)
rir, _ = torchaudio.load("room_impulse_response.wav")  # RIR 파일
reverb = F.fftconvolve(waveform, rir)

# 페이드 인/아웃
fade = torchaudio.transforms.Fade(
    fade_in_len=sample_rate,      # 1초 페이드 인
    fade_out_len=sample_rate * 3  # 3초 페이드 아웃
)
faded = fade(waveform)

# VAD (Voice Activity Detection)
vad = torchaudio.transforms.Vad(sample_rate=16000)
speech_only = vad(waveform)  # 무음 구간 제거

Part 6: 실전 프로젝트

환경음 분류 (Audio Classification)

import torch.nn as nn

class AudioClassifier(nn.Module):
    def __init__(self, n_classes=10):
        super().__init__()
        self.mel = torchaudio.transforms.MelSpectrogram(
            sample_rate=16000, n_fft=1024, n_mels=64
        )
        self.db = torchaudio.transforms.AmplitudeToDB()

        # Mel 스펙트로그램을 "이미지"처럼 CNN에 입력!
        self.cnn = nn.Sequential(
            nn.Conv2d(1, 32, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
            nn.Conv2d(64, 128, 3, padding=1), nn.ReLU(),
            nn.AdaptiveAvgPool2d((1, 1)),
        )
        self.fc = nn.Linear(128, n_classes)

    def forward(self, waveform):
        # [B, 1, samples] → [B, 1, n_mels, time]
        x = self.mel(waveform)
        x = self.db(x)
        x = self.cnn(x)
        x = x.view(x.size(0), -1)
        return self.fc(x)

# Mel 스펙트로그램 = 오디오의 "이미지"
# → CNN(ResNet, EfficientNet)으로 분류 가능!

실시간 스트리밍 처리

from torchaudio.io import StreamReader

# 마이크 입력 실시간 처리
reader = StreamReader(src=":0", format="avfoundation")  # macOS
reader.add_basic_audio_stream(
    frames_per_chunk=16000,  # 1초 단위
    sample_rate=16000,
)

for (chunk,) in reader.stream():
    # chunk: [1, 16000]
    mel = mel_transform(chunk)
    with torch.no_grad():
        prediction = model(mel)
    print(f"감지: {labels[prediction.argmax()]}")

📝 퀴즈 — torchaudio (클릭해서 확인!)

Q1. Mel 스케일이 필요한 이유는? ||인간의 청각은 저주파에 민감하고 고주파에 둔감. Mel 스케일은 이를 반영하여 저주파는 세밀하게, 고주파는 뭉뚱그려서 분석. 딥러닝 모델에 인간 청각 특성 반영||

Q2. n_fft를 키우면 어떤 해상도가 올라가고, 어떤 해상도가 내려가나? ||n_fft ↑ → 주파수 해상도 ↑ (세밀한 주파수 구분), 시간 해상도 ↓ (시간 변화 파악 어려움). 불확정성 원리와 유사한 트레이드오프||

Q3. SpecAugment의 두 가지 마스킹은? ||Time Masking: 시간 축에서 연속 프레임을 0으로 마스킹. Frequency Masking: 주파수 축에서 연속 채널을 0으로 마스킹. 데이터 augmentation으로 음성인식 정확도 크게 향상||

Q4. MFCC와 Mel Spectrogram의 차이와 각각의 용도는? ||MFCC: Mel Spectrogram에 DCT를 적용해 계수 추출 (13~40차원). 전통 음성인식, 화자 인식에 사용. Mel Spectrogram: 주파수-시간 2D 표현. 딥러닝 모델에 직접 입력 (최신 트렌드)||

Q5. Forced Alignment의 활용 사례는? ||음성과 텍스트의 시간 정렬. 자막 생성 (정확한 타이밍), 가사 동기화 (노래방), 발음 평가 (언어 학습 앱)||

Q6. Wav2Vec 2.0에서 CTC 디코딩의 blank 토큰 역할은? ||연속된 같은 토큰을 구분하고, 아무 출력도 없는 시간 구간을 표현. Greedy 디코딩에서 blank(index 0)과 연속 중복을 제거하여 최종 텍스트 생성||

Q7. Mel Spectrogram을 CNN에 넣을 수 있는 이유는? ||Mel Spectrogram은 2D 이미지와 같은 구조 (주파수 축 × 시간 축). 1채널 grayscale 이미지로 취급하여 ResNet, EfficientNet 등 이미지 분류 모델을 그대로 활용 가능||

📖 관련 시리즈 & 추천 포스팅

GitHub