개발이모저모

[python] 오디오 분석 라이브러리 librosa 뚝배기 깨기 - 오디오 불러오기

아티스트갓건 2022. 11. 9. 12:16

퇴사로 인해 야인이 된 기념으로 librosa의 뚝배기를 깨보려한다

https://librosa.org/doc/latest/index.html

 

librosa — librosa 0.9.2 documentation

© Copyright 2013--2022, librosa development team.

librosa.org

사실 위의 공식 홈페이지만 봐도 웬만하면 다 할 줄 알게 된다. 하지만 음악 & 오디오 관련 전문가이지만 코딩이 서툰 경우, 개발자이지만 음악 & 오디오 전문가가 아닌 분들께 요만큼이라도 도움이 되지 않을까 싶어서 작성한다. 극 초보자를 위한 글임을 분명히 밝힌다. 글의 형식이나 인덱스가 보기 힘들 수 있는데 그런 건 알아서 하길 바라며, 찐 이론적이고 자세한 설명은 생략될 가능성이 다분히 높기에 궁금하거나 그럴 땐 댓글 남겨주면 자세히 답변을 하겠다. 모르는 건 모른다고 할 것이다.

 

오디오 불러오는 여러 가지 방법

1. librosa.load( path )

import librosa

y, sr = librosa.load('test.wav')

print(y)
print(y.shape)
print(sr)

>> [-5.2294698e-05  5.1245124e-06  2.7739889e-06 ... -1.4241479e-07
 -4.7816047e-06  0.0000000e+00]
>> (5477356,)
>> 22050

변수 y에는 오디오 데이터, sr은 샘플 레이트(sample rate)가 저장된다. y, sr 또는 data, rate 등등 변수 이름은 자유롭게 사용하면 된다.

 

샘플 레이트는 오디오를 1초당 몇개의 조각으로 나눌지 결정하는 값이다. 샘플레이트가 44100이면 1초를 44100개의 샘플로 나누어서 파일에 기록한다. 오디오 파일같은걸 보다보면 44.1khz, 44100hz등을 종종 볼 수 있는데, 이게 그거다.

 

샘플 레이트는 음악 작업을 하던 음향작업을 하던 데이터 작업을 하던간에 통일시켜주는게 국룰이다. 만약 똑같이 1초짜리 오디오 데이터가 있는데, A의 샘플레이트는 44100이고 B의 샘플 레이트가 48000이라면, 재생을 시켰을 땐 동일하게 1초일지라도 A와 B안에 포함된 데이터의 숫자는 다르기 때문에 경우에 따라 낭패를 볼 수 있다. 48000짜리 데이터를 44100으로 처리를 한다거나 그 반대가 될 경우, 오디오 시간 값이 달라질 수도 있다! 이러면 그 프로젝트는 조지는 거다.

 

만약 본인의 데이터 셋에 포함된 오디오 파일의 샘플 레이트가 제각각인 경우, 이걸 미리 하나의 샘플레이트로 통일을 한다음에 작업하는 것을 추천한다. 파이썬으로 오디오 파일 포멧 변경 하는 것은 나중에 시간나면 다루도록 하겠다.

 

위의 코드의 경우 샘플레이트가 22050으로 나오는데, 이는 44100의 딱 절반이다. 프로 오디오의 경우 44100이나 48000을 많이 쓰지만 데이터 처리를 할 경우 보통 16000~22050으로 많이 한다더라. librosa는 디폴트 값으로 22050으로 설정되어있다.

 

좀 더 상세한 librosa.load

import librosa

# librosa.load의 속성값 등을 변경해보자!
y, sr = librosa.load(
    path = 'test.wav',
    sr = 44100,
    mono = False,
    duration = 10
    )

print(y)
print(y.shape)
print(sr)

>> [[-3.6621094e-04  3.3569336e-04 -3.0517578e-04 ... -1.1657715e-02
  -2.1911621e-02 -3.6682129e-02]
 [-6.1035156e-05  3.0517578e-05  0.0000000e+00 ...  8.8439941e-02
   6.7901611e-02  3.9794922e-02]]
>> (2, 441000)
>> 44100

librosa.load를 쓸 때 위의 코드에 적힌 값들 말고는 거의 써본 적이 없다.

path는 데이터가 있는 위치,

sr은 디폴트 값이 22050인데 44100으로 바꿔주었다. 48000으로 바꿔도 되고 156987486153으로 바꿔도 상관은 없지만 국 룰 샘플 레이트는 8000, 16000, 22050, 32000, 44100, 48000, 96000 이다. 웬만하면 원본 샘플레이트나 원본 샘플레이트 / 2의 값으로 하자.

mono는 모노 스테레오 할 때 그 모노이다. 기본값은 True이고, 그대로 설정하면 원본 파일이 모노던 스테레오던 무조건 모노로 처리한다. 하지만 만약 원본 파일이 모노라면 저걸 False로 해도 스테레오 파일로 처리되지는 않으니 알아두길 바란다.

duration은 초이다. 10으로 설정을 했으니 10초만 불러오게 된다. 원본 파일이 10초보다 작을 경우 그냥 원본 파일의 길이가 불러와진다.

 

2. soundfile.read(path)

import librosa
import soundfile as sf

y, sr_o = sf.read('test.mp3')

print(y) 
>> [[ 0.00000000e+00  0.00000000e+00]
 [-1.94129109e-15 -4.71774101e-15]
 [-1.01558107e-15 -3.59145146e-15]
 ...
 [ 0.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00]]
print(y.shape) # (9421824, 2) << 스테레오 형식
print(sr_o) # 44100

# 만약 불러온 파일이 스테레오인데 모노로 바꾸고 싶다면
if len(y.shape) == 2:
    y = (y[:,0] + y[:,1]) / 2
print(y.shape) # (9421824,) << 모노 형식

sr_t = 22050 # 내가 원하는 샘플레이트
y = librosa.resample(y=y, orig_sr = sr_o, target_sr = sr_t)
print(y.shape) # (4710912,) << 샘플레이트가 반으로 줄었으므로 데이터 길이도 반으로 줄어듬

mp3 확장자의 경우, 대부분의 오디오 개발에선 환영받지 못하는 포맷이다. mp3는 확장자 자체가 무슨 저작권인가 그런 게 걸려있어서 잘 지원을 안 한다. 하지만 작업을 하다 보면 mp3를 써야 되는 경우도 있는데, 최근 mac os에서 librosa가 mp3를 지원 안 하는지 librosa.load로 mp3를 로드하면 에러가 뜬다... 이럴 땐 위의 코드를 응용해서 사용하면 된다.

 

soundfile은 librosa가 오디오 데이터를 불러올 때 사용하는 종속 라이브러리다. 그냥 librosa만 설치하면 알아서 설치된다.

데이터를 불러오는 속도는 soundfile이 훨씬 빠르다. 다만 librosa처럼 알아서 데이터를 모노로 바꿔주거나 샘플 레이트 조정을 안 해주기 때문에 위의 코드를 사용하거나 아니면 애초에 불러오려는 파일의 포맷을 맞춰주면 훨씬 빠른 속도로 데이터를 불러올 수 있다.

 

다만 데이터를 불러왔을 때 librosa와 soundfile의 값을 비교해보면 묘하게 다르다

import librosa
import soundfile as sf

y, sr = librosa.load('test.wav', mono=False, sr = 44100)
print(y)
print(y.shape)
print(sr)
>>
[[-3.6621094e-04  3.3569336e-04 -3.0517578e-04 ... -9.1552734e-05
  -3.0517578e-05  1.5258789e-04]
 [-6.1035156e-05  3.0517578e-05  0.0000000e+00 ...  1.5258789e-04
  -9.1552734e-05  0.0000000e+00]]
>> (2, 10954711)
>> 44100

y2, sr2 = sf.read('test.wav')
print(y2)
print(y2.shape)
print(sr2)

>>
[[-3.66210938e-04 -6.10351562e-05]
 [ 3.35693359e-04  3.05175781e-05]
 [-3.05175781e-04  0.00000000e+00]
 ...
 [-9.15527344e-05  1.52587891e-04]
 [-3.05175781e-05 -9.15527344e-05]
 [ 1.52587891e-04  0.00000000e+00]]
>> (10954711, 2)
>> 44100

librosa로 불러왔을 땐 배열 모양이 (2, 10954711)인데 soundfile로 불러왔을 땐 (10954711, 2)로 뭔가 같으면서도 다르다. 구체적으로 무슨 차이가 있는지 보기 위해 0번째 인덱스 원소만 출력해보자

print(y[0])
>> [-3.6621094e-04  3.3569336e-04 -3.0517578e-04 ... -9.1552734e-05
 -3.0517578e-05  1.5258789e-04]
 
print(y2[0]) 
>> [-3.66210938e-04 -6.10351562e-05]

librosa로 불러온 데이터는 L, R을 각각 배열로 저장하고 soundfile로 불러온 데이터는 L,R을 하나의 원소로 배열을 저장된다. 왜 그런지는 궁금해할 필요가 없다. 우리는 앞으로 librosa만 쓸 것이기 때문에, soundfile로 데이터를 불러왔다면 기존 librosa의 형식으로 바꿔줘야 한다

이것을 바꿔주기 위해 numpy라는 라이브러리를 사용한다

#위에 코드에 아래 코드를 추가하세요
import numpy as np

print(y)
>> [[-3.66210938e-04  3.35693359e-04 -3.05175781e-04 ... -9.15527344e-05
  -3.05175781e-05  1.52587891e-04]
 [-6.10351562e-05  3.05175781e-05  0.00000000e+00 ...  1.52587891e-04
  -9.15527344e-05  0.00000000e+00]]

# 킹갓제너럴 numpy 사용
y2 = np.swapaxes(y2, 0, 1) # 또는 y2 = np.array([y2[:,0], y2[:,1]])

print(y2)
>> [[-3.66210938e-04  3.35693359e-04 -3.05175781e-04 ... -9.15527344e-05
  -3.05175781e-05  1.52587891e-04]
 [-6.10351562e-05  3.05175781e-05  0.00000000e+00 ...  1.52587891e-04
  -9.15527344e-05  0.00000000e+00]]

librosa는 soundfile뿐 아니라 numpy도 사용한다. 오디오 처리를 할 때 numpy는 무진장 많이 사용하기 때문에 알아두면 좋다.

np.swapaxes 는 배열의 행열 축을 바꿔주는 기능을 한다. 옆에 주석으로 '또는' 뒤에 나오는 코드랑 현재 상황에선 똑같은 기능을 한다.

 

 

Stereo > Mono 수동 바꾸기

아날로그 오디오 신호에서 Stereo를 Mono로 처리하고 싶은 경우엔 L, R 중  L 채널만 쓰는 게 국 룰이다. 하지만 librosa가 자동으로 모노 데이터를 만드는 방식은 L, R의 신호를 그냥 합치는 것이다. 이렇게 되면 위상(Phase) 문제가 있는 데이터의 경우 데이터가 상쇄되는 문제가 있는데, 그럼에도 불구하고 이렇게 쓴다는 건 내가 모르는 디지털 세계의 국 룰인가 싶기도 하다.

그냥 살면서 이런 경우도 있구나 생각하고 알아두길 바란다.

librosa로 불러올 때 수동으로 mono를 만드는 방법

import librosa

# librosa를 이용해 모노로 불러오는 경우
yMono, _ = librosa.load('test.wav', mono = True, sr = 44100)
print(yMono)
>> [-2.1362305e-04  1.8310547e-04 -1.5258789e-04 ...  3.0517578e-05
 -6.1035156e-05  7.6293945e-05]

# librosa를 이용해 스테레오로 불러온 다음 모노로 변경하는 경우
y, sr = librosa.load('test.wav', mono=False, sr = 44100)
print(y)
>> [[-3.6621094e-04  3.3569336e-04 -3.0517578e-04 ... -9.1552734e-05
  -3.0517578e-05  1.5258789e-04]
 [-6.1035156e-05  3.0517578e-05  0.0000000e+00 ...  1.5258789e-04
  -9.1552734e-05  0.0000000e+00]]

yL = y[0] # Left 채널을 Mono로 사용
print(yL)
>> [-3.6621094e-04  3.3569336e-04 -3.0517578e-04 ... -9.1552734e-05
 -3.0517578e-05  1.5258789e-04]

yR = y[1] # Right 채널을 Mono로 사용
print(yR)
>> [-6.1035156e-05  3.0517578e-05  0.0000000e+00 ...  1.5258789e-04
 -9.1552734e-05  0.0000000e+00]
 
yS = (y[0] + y[1]) / 2 # Left와 Right를 더한 후 2로 나누는 경우 (자동으로 Mono로 불러올 때의 값과 동일)
print(yS)
>> [-2.1362305e-04  1.8310547e-04 -1.5258789e-04 ...  3.0517578e-05
 -6.1035156e-05  7.6293945e-05]

soundfile로 불러올 때 수동으로 mono를 만드는 방법

import soundfile as sf

y, sr = sf.read('test.wav')
print(y)
>> [[-3.66210938e-04 -6.10351562e-05]
 [ 3.35693359e-04  3.05175781e-05]
 [-3.05175781e-04  0.00000000e+00]
 ...
 [-9.15527344e-05  1.52587891e-04]
 [-3.05175781e-05 -9.15527344e-05]
 [ 1.52587891e-04  0.00000000e+00]]

yL = y[:,0]
print(yL)
>> [-3.66210938e-04  3.35693359e-04 -3.05175781e-04 ... -9.15527344e-05
 -3.05175781e-05  1.52587891e-04]


yR = y[:,1]
print(yR)
>> [-6.10351562e-05  3.05175781e-05  0.00000000e+00 ...  1.52587891e-04
 -9.15527344e-05  0.00000000e+00]

yS = (y[:,0] + y[:,1]) / 2
print(yS)
>> [-2.13623047e-04  1.83105469e-04 -1.52587891e-04 ...  3.05175781e-05
 -6.10351562e-05  7.62939453e-05]

결과는 librosa에서 수동으로 모노를 만드는 방법과 같다.

위의 코드에서 y [:,0] 따위의 코드를 볼 수 있는데, 가운데 있는 ', ' 이건 오타가 아니라.. numpy에서 특정 행만 추출하고자 할 때 쓰는 문법이다. numpy는 알아두는 게 좋다.

 

정리

고작 오디오 불러오는 거 갖고 참 길게도 써놓은 것 같은데, 본인이 고수라면 볼 필요도 없겠지만 아 내가 코딩 처음 배울 땐 이런 것도 알려주는 사람이 없어서 너무 슬펐다. 극 초보자를 위한 글임을 분명히 밝혀둔다는 것을 분명히 밝혔고, 혹시 틀렸거나 궁금한 게 있으면 알려주길 바란다.