DL/Voice

[Voice] Audio Processing Libraries (feat. 8bit convert to 16 bit)

moonzoo 2024. 9. 25. 16:22

0. 8bit convert to 16 bit

8k 8bit PCM 음성 신호를 8k 16bit PCM으로 변환하는 것은 음성의 질을 높이는데 도움이 됩니다.

또한, 8-bit 음성은 2^8 = 256단계로 소리를 표현하는 반면, 16-bit 음성은 2^16 = 65,536단계로 소리를 표현할 수 있습니다. 이 차이는 음성 신호의 더 작은 차이까지도 표현할 수 있게 해 주며, 더 풍부한 정보를 담은 음성을 제공합니다.  

이러한 이유로 8k 8bit 음성을 8k 16bit로 변환하여 자주 사용하고 있습니다.

 


1. 변환 왜곡

 

하지만 여기서 라이브러리를 사용해 음성을 16bit로 변환하면 음성에 왜곡이 발생합니다. 

 

int8과 int16가 표현할 수 있는 범위는 아래와 같습니다.

 

  • int8 (8-bit integer):
    • Signed (부호가 있는 경우): -128에서 127까지
    • Unsigned (부호가 없는 경우): 0에서 255까지
  • int16 (16-bit integer):
    • Signed (부호가 있는 경우): -32,768에서 32,767까지
    • Unsigned (부호가 없는 경우): 0에서 65,535까지

 

이러한 경우 int8 format의 음성은 [-128, 127] 각 샘플에서 범위로 표현이 돼있을 것인데, 이를 int 16 변환을 위해 *256을 해준다면 그 범위는 [-32768, 32512]가 됩니다.

 

여기서 확인할 수 있는 문제는 원래 int16의 범위는 [-32768, 32767]인데, int8 * 256은 int16의 최대 값인 32767까지 도달하지 못합니다. 이로 인해 음성 신호의 최대값이 왜곡되어 음질에 영향을 미칠 수 있습니다.

 

(사실 [-32768, 32512]로 변환이되더라도 실제로 음성을 들어보면 큰 차이는 없어 보이긴 하나, 정확한 연산을 요구할 때에는 이러한 세세한 점도 챙겨야 할 필요가 있습니다.)

 

그렇다면 Audio Processing Libraries 들은 어떤식으로 8bit 음성을 16bit로 변환하는지 확인해보겠습니다.

2. Audio Processing Libraries 특징 

NumPy와 SciPy

  • 기본적인 수치 연산과 신호 처리 기능 제공
  • 오디오 데이터를 다차원 배열로 처리 가능
  • 저수준 연산에 적합하지만 고수준 오디오 기능은 제한적

librosa

  • 음악 및 오디오 분석에 특화
  • 특징 추출, 비트 탐지, 스펙트로그램 생성 등 다양한 기능 제공
  • 음악 정보 검색(MIR) 작업에 널리 사용됨

soundfile

  • 다양한 오디오 파일 형식 읽기/쓰기 지원
  • 간단하고 사용하기 쉬운 인터페이스 제공
  • WAV, FLAC 등 여러 포맷 지원

PyDub

  • 높은 수준의 인터페이스로 오디오 조작 간소화
  • MP3 등 다양한 파일 형식 지원
  • 오디오 세그먼트 조작, 효과 적용 등 가능

wave

  • 파이썬 표준 라이브러리에 포함된 모듈
  • WAV 파일 읽기, 쓰기, 기본적인 정보 추출 기능 제공
  • 간단한 인터페이스로 WAV 파일 조작 가능
  • 16비트 이하의 WAV 파일만 지원

PyWave

  • wave 모듈의 확장 버전으로 개발된 서드파티 라이브러리
  • 16비트 이상의 고해상도 WAV 파일 지원
  • wave 모듈을 대체할 목적으로 만들어짐
  • 메타데이터 처리 기능 추가

 

3. 라이브러리 변환 값 비교

비트 깊이 변환은 단순히 형식을 바꾸는 것이 아니라, 8비트 범위를 16비트 범위로 확장하는 과정이기 때문에 어떤 라이브러리도 내부적으로 변환 과정을 거치게 됩니다. 다만, 이러한 과정을 내부적으로 자동 처리하는 라이브러리가 있습니다.

 

예시로는 두 가지가 있습니다. AudioSegment와 ffmpeg 

 

 

AudioSegment

# 8bit 16 bit 변환 예시
import numpy as np
from scipy.io.wavfile import write, read
from pydub import AudioSegment

# Create the int8 array
int8_array = np.array([0,255], dtype=np.uint8)

# Save it as an 8kHz, 8-bit wav file
wav_filename = "./int8_audio.wav"
write(wav_filename, 8000, int8_array)

# Load the wav file using pydub and convert to 16-bit
audio_segment = AudioSegment.from_wav(wav_filename)
audio_segment_16bit = audio_segment.set_sample_width(2)  # 16 bits = 2 bytes

# Extract the array from the 16-bit audio
samples = np.array(audio_segment_16bit.get_array_of_samples())

samples
array ([-32768, 32512], dtype = int16)

 

uint8 범위인 [0, 255]를 pydub.AudioSegment를 통해 16bit로 변환했을 때의 샘플의 값은 [-32768, 32512]로 변환되며 값이 왜곡되고 있습니다.

 

 

ffmpeg

 

ffmpeg -i int8_audio.wav -acodec pcm_s16le -ar 8000 16bit_audio.wav

 

import wave
import numpy as np

# 16비트 wav 파일 불러오기
wav_16bit_filename = "16bit_audio.wav"
with wave.open(wav_16bit_filename, 'rb') as wav_file:
    params = wav_file.getparams()
    n_channels, sampwidth, framerate, n_frames = params[:4]
    frames = wav_file.readframes(n_frames)
    int16_samples = np.frombuffer(frames, dtype=np.int16)  # 16비트 데이터로 읽음

# 샘플 값 출력
print(int16_samples)

 

array ([-32768, 32512], dtype = int16)

 

마찬가지로 샘플의 값은 [-32768, 32512]로 변환되며 값이 왜곡 됩니다.

 

Other

 

이외 라이브러리들은 8비트 데이터를 16비트로 바꾸는 작업을 진행하고, 16 bit wav 파일로 저장해야 하기 때문에 다루지 않았습니다. 8비트 데이터를 16비트 데이터로 바꾸는 연산 과정은 코드로 직접 작성해야 하기 때문에 다루지 않겠습니다.

 

결론

 

결국 내부적으로 변환하는 라이브러리 모두 16비트로 변환할 때, [-32768, 32512]로 변환하고 있어 int16의 최대 값인 32767까지 도달하지 못해 최댓값이 왜곡됩니다.

 


4. 솔루션

array = np.array([0,255,255,0],dtype =np.uint8)
audio_data_16bit = (array.astype(np.int32) * 257) - 32768
audio_data_16bit = audio_data_16bit.astype(np.int16)

 

array([-32768, 32767, 32767, -32768], dtype=int16)

 

audio_data_16bit = (array.astype(np.int32) * 257) - 32768
  • array.astype(np.int32): uint8 배열을 int32로 변환합니다. 이렇게 변환하는 이유는 8비트에서 16비트로 변환하는 과정에서 오버플로우를 방지하고 정확한 연산을 하기 위함입니다.
  • * 257: 8비트 데이터를 16비트로 변환하는 핵심 부분입니다. 8비트 오디오 데이터(0 ~ 255)는 16비트 데이터(-32768 ~ 32767)로 확장되어야 합니다. 16비트 오디오에서 1단위는 8비트의 약 257배가 되기 때문에, * 257을 적용하여 값을 확장합니다. 
변환 결과
0 * 257 = 0
255 * 257 = 65535
  • - 32768: 16비트 오디오 데이터는 중앙값이 0을 기준으로 대칭적이어야 합니다. 8비트 오디오에서는 0이 최소값이고 255가 최대값인 반면, 16비트에서는 -32768이 최소값이고 32767이 최대값입니다. 이를 맞추기 위해 모든 값에 -32768을 더해줍니다.
변환 결과
0 * 257 - 32768 = -32768
255 * 257 - 32768 = 32767
audio_data_16bit = audio_data_16bit.astype(np.int16)
  • 최종적으로 변환된 데이터를 int32에서 int16으로 다시 변환합니다. 이렇게 해야 데이터가 실제로 16비트 형식에 맞게 저장될 수 있습니다.

이렇게 해서 8비트를 16비트의 범위인 [-32768, 32767]로 변환하실 수 있습니다.

 

여기서 int32로 변환하지 않고 float32로 변환하면 부동소수점 단위까지 포함하기 때문에 더 정확한 연산이 가능합니다.

또한, 지금은 간단하게 257을 곱해서 범위를 조정했는데 다른 방법으로 uint8 -> uint16 -> int16 변환을 진행하는 것이 더 정확합니다.

 


5. 결론

8bit 오디오를 16bit로 변환 시 라이브러리를 사용하면 정보가 일부 손실될 수 있다.

이는 클리핑을 방지하기 위한 수단일 수도 있으나, 특정 상황에서는 정확한 변환이 필요할 수 있다.

또한, 실제로 최댓값에 도달하는 경우도 거의 없기 때문에 큰 문제가 되지는 않는다.

 

오디오 신호 처리를 통해 이용하는 환경에 따라 적절한 방법을 선택해 사용하면 될 듯 하다.