본문 바로가기
데이터&AI/기타개발

[realtime GPT를 쓰기위한 사전학습]pyaudio이해하기② (데이터를 소리로 전환!!!)

by 일등박사 2024. 10. 23.
728x90

 

2024.10.20 - [데이터&AI/LLM] - [realtime GPT를 쓰기위한 사전학습]pyaudio이해하기① (마이크 음성을 데이터로 전환!!!)

 

 

 

지난 포스팅에 이어 pyaudio 에 대하여 더 알아보겠습니다!!

 


오늘은!!

지난번 소리를 데이터로 전환하는것과 반대로!!

데이터를 소리로 전환해보겠습니다!!

 

OpenAI로부터 응답이온 데이터를 소리로 만들어서 스피커가 켤수 있어야겠찌요!?

 

 

 

묻고 따지지 않고 아래 코드를 실행해봅다!!

 

※ 주의!!  전자음이 나오니 스피커 소리를 작게하고 틀어주세요!!

import numpy as np
import pyaudio

# PyAudio 설정
FORMAT = pyaudio.paInt16  # 16비트 오디오
CHANNELS = 1              # 모노 채널
RATE = 44100              # 샘플링 레이트 (44.1kHz)
DURATION = 2              # 삐 소리의 지속 시간 (초)
FREQUENCY = 1000          # 삐 소리의 주파수 (1000Hz)

# PyAudio 초기화
p = pyaudio.PyAudio()

# 사인파 생성 함수 (삐 소리 데이터 생성)
def generate_beep(frequency, duration, rate):
    t = np.linspace(0, duration, int(rate * duration), False)  # 시간 배열 생성
    # 사인파 생성 (16비트 정수형)
    beep_data = np.sin(2 * np.pi * frequency * t) * 32767  # 16비트로 변환
    return beep_data.astype(np.int16).tobytes()  # 바이트 형태로 변환하여 반환

# 스피커로 소리 출력하는 콜백 함수
def _spkr_callback(in_data, frame_count, time_info, status):
    # 스피커로 출력할 오디오 데이터
    audio_chunk = beep_data[:frame_count * 2]  # 필요한 만큼 청크로 자름
    return (audio_chunk, pyaudio.paContinue)

# 삐 소리 데이터 생성 (주파수 1000Hz, 지속 시간 2초)
beep_data = generate_beep(FREQUENCY, DURATION, RATE)

# PyAudio 출력 스트림 설정 (스피커로 출력)
stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                output=True,  # 스피커 출력
                frames_per_buffer=1024,
                stream_callback=_spkr_callback)

# 스트림 시작
stream.start_stream()

# 삐 소리 출력하는 동안 대기
print("삐 소리가 스피커로 출력됩니다.")
while stream.is_active():
    pass

# 스트림 종료
stream.stop_stream()
stream.close()
p.terminate()

 

 

실행해보니 어떤가요!?

삐~~~~~~ 하는 소리가 나지요!? 

그 원리를 파악해봅시다~!

 

1. 소리데이터 만들기

 > 실제로는 OpenAI의 socket에서 답변온 데이터로 재생하겠지만,

 > 지금은 데이터를 만들어서 테스트해야합니다! 이에!

 > 소리데이터 생성 부분은 아래와 같습니다!

 > 쉽게 생각하면 사인파형의 데이터를 생성하는것이지요~~

# 사인파 생성 함수 (삐 소리 데이터 생성)
def generate_beep(frequency, duration, rate):
    t = np.linspace(0, duration, int(rate * duration), False)  # 시간 배열 생성
    # 사인파 생성 (16비트 정수형)
    beep_data = np.sin(2 * np.pi * frequency * t) * 32767  # 16비트로 변환
    return beep_data.astype(np.int16).tobytes()  # 바이트 형태로 변환하여 반환

간단히 numpy array를 확인해보면!

 

위와 같이 숫자의 모음? 이라고 생각하며됩니다!!

 

2. call_back함수 이해!

지난 포스팅에서 indata로 반영되었던 call back 함수 기억나시나요~!!?

이번에도 마찬가지인데요!! "_spkr_callback" 라는 함수를 만들고,

pyaudio 객채를 만들때 아래와 같이 callback 함수로 선언해줍니다!

# PyAudio 초기화
p = pyaudio.PyAudio()

# PyAudio 출력 스트림 설정 (스피커로 출력)
stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                output=True,  # 스피커 출력
                frames_per_buffer=1024,
                stream_callback=_spkr_callback)

그리고!!  "_spkr_callback" 함수를 살펴보면!?

# 스피커로 소리 출력하는 콜백 함수
def _spkr_callback(in_data, frame_count, time_info, status):
    # 스피커로 출력할 오디오 데이터
    audio_chunk = beep_data[:frame_count * 2]  # 필요한 만큼 청크로 자름
    return (audio_chunk, pyaudio.paContinue)

 

위와 같이!! 여기서도 in_data로 데이터가 들어오고,

(이번 테스트의 경우 외부에서 오는 데이터가 없기에 in_data는 사용되지 않지만요@!)

 

이후 여기서는 위의 generate_beep 로 생성된 beep_data를 audio_chunk로 만들고!!

audio_chunk는 PyAudio에 의해 스피커로 출력되도록 반환됩니다. 

 

콜백 함수는 매번 호출될 때마다 이 데이터를 PyAudio에 전달하며, PyAudio는 그 데이터를 스피커로 출력합니다.

 


이해가 되셨나요!?

이제는 마지막으로!! 내 목소리를 데이터로 저장하고, 그 데이터를 불러와서 스피커로 실행해보는 마지막 테스트를 진행해 보겠습니다!

3. 마지막 복습 : 목소리 > 데이터 > 소리

아래와 같은 순서로 진행됩니다!!

1.  record_voice : 내 목소리가 녹음되고!! numpy 배열이 저장

2. play_voice : numpy 로 저장된 데이터를 불러와서 소리로 실행!!

 

아래 코드를 봅시다!! 

import pyaudio
import numpy as np

# PyAudio 설정
FORMAT = pyaudio.paInt16  # 16비트 오디오
CHANNELS = 1              # 모노 채널
RATE = 44100              # 샘플링 레이트 (44.1kHz)
CHUNK = 1024              # 버퍼 크기
RECORD_SECONDS = 5        # 녹음 시간 (5초)
AUDIO_FILE = "audio_data.txt"  # 저장할 텍스트 파일 이름

# PyAudio 초기화
p = pyaudio.PyAudio()

# 1. 마이크로부터 오디오를 입력받아 NumPy 배열에 저장하고, 텍스트 파일로 저장하는 함수
def record_voice():
    print("녹음을 시작합니다. 마이크에 대고 말하세요...")

    # 입력 스트림 설정 (마이크에서 입력받음)
    stream = p.open(format=FORMAT,
                    channels=CHANNELS,
                    rate=RATE,
                    input=True,
                    frames_per_buffer=CHUNK)

    frames = []  # 오디오 데이터를 저장할 리스트

    # CHUNK 크기만큼 데이터를 읽고 저장 (RECORD_SECONDS 동안 반복)
    for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
        data = stream.read(CHUNK)
        frames.append(np.frombuffer(data, dtype=np.int16))  # 데이터를 NumPy 배열로 변환

    print("녹음이 끝났습니다.")

    # 스트림 종료
    stream.stop_stream()
    stream.close()

    # NumPy 배열로 변환
    audio_data = np.hstack(frames)  # 모든 데이터를 하나의 NumPy 배열로 합침

    # NumPy 배열을 텍스트 파일로 저장
    np.savetxt(AUDIO_FILE, audio_data, fmt='%d')  # 정수 형태로 저장

    print(f"오디오 데이터가 {AUDIO_FILE}에 저장되었습니다.")

# 2. 텍스트 파일에서 NumPy 배열을 불러와 스피커로 재생하는 함수
def play_voice():
    print(f"{AUDIO_FILE}에서 오디오 데이터를 불러옵니다...")

    # 텍스트 파일에서 NumPy 배열 불러오기
    audio_data = np.loadtxt(AUDIO_FILE, dtype=np.int16)

    print("저장된 목소리를 재생합니다...")

    # 출력 스트림 설정 (스피커로 출력)
    stream = p.open(format=FORMAT,
                    channels=CHANNELS,
                    rate=RATE,
                    output=True)

    # NumPy 배열 데이터를 CHUNK 크기로 나누어 재생
    start = 0
    while start < len(audio_data):
        end = min(start + CHUNK, len(audio_data))
        stream.write(audio_data[start:end].tobytes())  # 데이터를 바이트로 변환 후 출력
        start = end

    # 스트림 종료
    stream.stop_stream()
    stream.close()

# PyAudio 종료
def terminate_pyaudio():
    p.terminate()

# 3. 전체 실행 순서
if __name__ == "__main__":
    # 목소리 녹음 (NumPy 배열로 저장하고 텍스트 파일로 기록)
    record_voice()
    
    # 녹음된 데이터를 텍스트 파일에서 불러와 재생
    play_voice()

    # PyAudio 종료
    terminate_pyaudio()

 

코드를 실행해보면!!

 

녹음된 목소리가 정말 잘 실행되구요!!

 

내 디렉토리에는 audio_data.txt라는 텍스트 파일로!!

220127 개의 배열 데이터가 잘 저장됨을 확인할 수 있었습니다!

 

 

 

지금까지!! pyaudio를 활용하여!

소리를 데이터로! 그리규 데이터를 소리로 바꾸는 방법에 대하여 알아보았습니다!!

728x90

댓글