파이썬에서 오디오 파일 다루기(pydub, soundfile)
2025. 5. 25. 22:14ㆍ연구하기, 지식
서론
- 파이썬에는 다양한 오디오 관련 라이브러리가 있다. 필자는 주로 pydub 라이브러리를 사용한다. 사용하기 간편하고 Numpy 배열로 오디오를 불러오는 방식이 아니어서 조금 더 직관적이기 때문이다. 하지만 pydub을 꾸준히 사용하며 느낀 몇가지 단점들이 있다. 모두 Numpy 배열을 사용하지 않아 일어나는 단점이다.
- 연산 작업을 하면 느리다.
- 다른 작업 시 I/O가 개입되지 않으면 활용이 제한된다.
- 정밀한 신호 처리가 어렵다.
- 이러한 단점들때문에 사용하기 어려울 때가 있다. 특히 빠른 처리(실시간 환경 등)에서 오디오를 처리해야 할 때는 pydub이 성능을 떨어트린다. 그래서 soundfile 라이브러리를 사용해보려고 한다.
pydub
GitHub - jiaaro/pydub: Manipulate audio with a simple and easy high level interface
Manipulate audio with a simple and easy high level interface - jiaaro/pydub
github.com
from pydub import AudioSegment
meeting_audio_path = '/content/drive/MyDrive/treat_audio/2_people_audio.m4a'
man_audio_path = '/content/drive/MyDrive/treat_audio/man_introduce.m4a'
meeting_audio = AudioSegment.from_file(meeting_audio_path)
man_audio = AudioSegment.from_file(man_audio_path)
meeting_audio = meeting_audio[:3000]
man_audio = man_audio[:3000]
meeting_audio = meeting_audio.set_frame_rate(8000)
man_audio = man_audio.set_frame_rate(8000)
meeting_audio.export(meeting_audio_path.replace('.m4a', '.wav'), format='wav')
man_audio.export(man_audio_path.replace('.m4a', '.wav'), format='wav')
- 위 코드와 같이 pydub을 너무 간단하다. 정말 간편하게 오디오 길이를 자르고, 프레임 레이트를 설정하고, 포맷을 바꿔 저장할 수 있다. 하지만 저 오디오 변수들(예를 들면, meeting_audio)을 pydub 라이브러리가 아닌 다른 라이브러리에서 활용하긴 정말 어렵다. 그리고 청크 단위로 어떤 연산을 하려할 때 시간이 매우 오래 걸린다.
soundfile
- https://python-soundfile.readthedocs.io/en/0.13.1/
- soundfile 라이브러리는 오디오를 Numpy 배열로 불러와 다양한 연산을 빠르게 가능하다. 그리고 Numpy 배열이기 때문에 변수에 저장해서 다른 곳에서 사용할 수 있다. 이를 테면, 텐서 연산에도 활용할 수 있을 것 같다.
- sf.read() 함수를 사용하면 기본적으로 배열과 샘플레이트를 return 받을 수 있다. 오디오 배열은 1Hz 가 float 타입으로 한 칸을 차지하고 있다. 따라서 오디오의 길이도 출력이 가능하다. 샘플레이트가 1초안에 들어있는 오디오 신호이니 배열의 길이를 샘플레이트로 나누면 오디오 길이이다. 해당 아이디어를 기반으로 오디오도 자를 수 있다.
- 또한, 대용량 처리 및 실시간 처리를 용이하게 하기 위한 sf.soundFile() 함수도 있다. sf.read()는 오디오 파일 전체를 읽어오지만 sf.soundFile() 은 청크 단위로 읽어 오거나, seek를 사용하면 중간 부분만 읽어 올 수 있다. 위 코드 하단은 오디오의 10초 ~ 11초를 가져오는 부분이다. 몇 시간 단위 오디오 작업을할 때 매우 유용하다.
import soundfile as sf
input_path = '/content/drive/MyDrive/treat_audio/man_introduce.wav'
data, samplerate = sf.read(input_path)
reversed_data = data[::-1]
output_path = '/content/drive/MyDrive/treat_audio/man_introduce_reversed.wav'
sf.write(output_path, reversed_data, samplerate)
- Numpy로 오디오를 불러오다 보니 위와 같이 재미있는 기능도 가능하다. 배열을 거꾸로 뒤집어서 역행 오디오를 만들 수도 있다.
pydub vs soundfile
- 필자는 오디오의 다이나믹 레인지를 줄이기 위한 코드를 작성한 적이있다. 기본 아이디어는 오디오의 평균 dB 측정 -> 청크별 dB를 평균 dB로 맞추기 이다. 세 시간 분량의 오디오를 pydb으로 작업하니 너무 오랜 시간이 걸렸다. 따라서 soundfile로 라이브러리를 바꿨다. 기본적인 아이디어만 간단히 구현해 성능을 측정 해보겠다.
- pydub 10회 평균 실행 시간 : 0.7267초
from pydub import AudioSegment
import time
from tqdm import tqdm
input_path = '/content/drive/MyDrive/treat_audio/man_introduce.wav'
output_path = '/content/drive/MyDrive/treat_audio/man_introduce_test.wav'
time_list = []
for _ in tqdm(range(10)):
start_time = time.time()
audio = AudioSegment.from_file(input_path)
target_dBFS = audio.dBFS
adjusted = AudioSegment.empty()
for i in range(0, len(audio)):
frame = audio[i:i+1]
diff = target_dBFS - frame.dBFS if frame.dBFS != float('-inf') else 0
adjusted += frame.apply_gain(diff)
adjusted.export(output_path, format='wav')
end_time = time.time()
time_list.append(end_time - start_time)
print(sum(time_list) / len(time_list))
- soundfile 10회 평균 실행 시간 : 0.4150초
import soundfile as sf
import numpy as np
import time
from tqdm import tqdm
input_path = '/content/drive/MyDrive/treat_audio/man_introduce.wav'
output_path = '/content/drive/MyDrive/treat_audio/man_introduce_test.wav'
time_list = []
def calculate_dbfs(samples):
rms = np.sqrt(np.mean(samples**2))
if rms == 0:
return -np.inf
return 20 * np.log10(rms)
def apply_gain(samples, gain_dB):
factor = 10 ** (gain_dB / 20)
return samples * factor
for _ in tqdm(range(10)):
start_time = time.time()
data, sr = sf.read(input_path)
if data.ndim > 1:
data = np.mean(data, axis=1)
target_dbfs = calculate_dbfs(data)
samples_per_ms = int(sr / 1000)
adjusted_frames = []
for i in range(0, len(data), samples_per_ms):
frame = data[i:i+samples_per_ms]
if len(frame) == 0:
continue
frame_dbfs = calculate_dbfs(frame)
gain = target_dbfs - frame_dbfs if frame_dbfs != -np.inf else 0
adjusted_frame = apply_gain(frame, gain)
adjusted_frames.append(adjusted_frame)
adjusted_data = np.concatenate(adjusted_frames)
sf.write(output_path, adjusted_data, sr, subtype='PCM_16')
end_time = time.time()
time_list.append(end_time - start_time)
print(f'평균 처리 시간: {sum(time_list) / len(time_list):.4f}초')
- 확실히 저수준 코드라 soundfile 코드가 더 복잡하지만 실행 속도는 약 40% 정도 빠르다.
결론
- pydub은 오디오 포맷 변경(샘플레이트, 확장자 등), 단순 오디오 자르기 에서만 사용 해야겠다.
- sf.SoundFile 함수를 조금 더 분석 해서 스트리밍에 활용 해봐야겠다.
728x90
'연구하기, 지식' 카테고리의 다른 글
푸리에 변환(Fourier transform, FT) (2) | 2025.06.02 |
---|---|
NeMo Speaker Recognition(SR) 성능 올리기 (6) | 2025.05.25 |
API 통신과 소켓 통신 (4) | 2025.05.18 |
쿨백-라이블러 발산(Kullback–Leibler divergence, KLD) (1) | 2025.05.06 |
Stable Diffusion Basemodel 로컬에서 사용하기 (0) | 2025.05.06 |